Migrating Data from Database to new Smart Contract

Hi, I am currently trying to manually migrate data from a database which is associated with an ERC721 Smart Contract. I have listed the smart contract below.

I am basically creating a new upgradeable smart contract with an initializer with the data i already have stored in my database, but this data is associated with the old smart contract (listed below).

What is the process involved here? How does the new smart contract know it has the data from the old one? Do i need to write a function in the new smart contract or perhaps there is some sort of "file" insert/call that needs to be called within a function that points to this data?

Thanks

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract ERC721 is ERC721URIStorage, Pausable, AccessControl  {
  
  bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
  bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
  bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
  bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
  
  // Base URI
  string private _baseUri;
  address _forwarder;
  
  mapping(uint256 => string) private _digests;
  mapping(uint256 => string) private _infoRoots;
  
  // Addresses under operator control
  mapping(address => bool) private _operatorEnabled;
  
  constructor() public ERC721("", "") Pausable() { 
    _baseUri = "";
    _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
    _setupRole(PAUSER_ROLE, msg.sender);
    _setupRole(MINTER_ROLE, msg.sender);
    _setupRole(BURNER_ROLE, msg.sender);
    _setupRole(OPERATOR_ROLE, msg.sender);
  }
  
  function pause() external onlyRole(PAUSER_ROLE) {
    _pause();
  }

  function unpause() external onlyRole(PAUSER_ROLE) {
    _unpause();
  }

  /**
   * @dev See {ERC20-_beforeTokenTransfer}.
   * Taken from ERC20Pausable
   *
   * Requirements:
   *
   * - the contract must not be paused.
   */
  function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
    super._beforeTokenTransfer(from, to, amount);
    require(!paused(), "ERC721Pausable: token transfer while paused");
  }
  
  function mint(address to, uint256 tokenId, string memory tokenUri, string memory digest) public onlyRole(MINTER_ROLE) {
    _mint(to, tokenId);
    _setTokenURI(tokenId, tokenUri);
    _digests[tokenId] = digest;
  }

  function burn(uint256 tokenId) public onlyRole(BURNER_ROLE) {
    _burn(tokenId);
  }
  
  function setBaseURI(string memory uri) external onlyRole(OPERATOR_ROLE) {
    _baseUri = uri;
  }

  /**
   * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
   * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
   * by default, can be overriden in child contracts.
   */
  function _baseURI() internal view virtual override returns (string memory) {
    return _baseUri;
  }

  function infoRoot(uint256 tokenId) external view virtual returns (string memory) {
    require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");

    string memory _infoRoot = _infoRoots[tokenId];

    // If there is no infoRoot set, return an empty string.
    if (bytes(_infoRoot).length == 0) {
      return "";
    }

    return _infoRoot;
  }

  function setInfoRoot(uint256 tokenId, string memory _infoRoot) external onlyRole(OPERATOR_ROLE) whenNotPaused() {
    require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
    _infoRoots[tokenId] = _infoRoot;
  }
  
  function digest(uint256 tokenId) external view virtual returns (string memory) {
    require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");

    string memory digest = _digests[tokenId];

    // If there is no digest set, return an empty string.
    if (bytes(digest).length == 0) {
      return "";
    }

    return digest;
  }
  
  function setDigest(uint256 tokenId, string memory digest) external onlyRole(OPERATOR_ROLE) whenNotPaused() {
    require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
    _digests[tokenId] = digest;
  }

  // Operator initiatiated token transfer
  function operatorTransfer(address recipient, uint256 tokenId) external onlyRole(OPERATOR_ROLE) whenNotPaused() returns (bool) {
    address owner = ownerOf(tokenId);
    require(isOperatorControlled(owner), "ERC721: sender not under operator control");
    // Reset appoval
    _approve(msg.sender, tokenId);
    transferFrom(owner, recipient, tokenId);
    return true;
  }
  
  // Address owner can enable their address for operator control
  // Default state is operator disabled
  function enableOperatorControl() external whenNotPaused() returns (bool) {
    require(msgSender() != address(0), "ERC20: owner is a zero address");
    require(!isOperatorControlled(msgSender()), "ERC20: owner already under operator control");
    _operatorEnabled[msgSender()] = true;
    return true;
  }
  
  // Operator role can remove operator control from an address
  function disableOperatorControl(address owner) external onlyRole(OPERATOR_ROLE) whenNotPaused() returns (bool) {
    require(owner != address(0), "ERC721: owner is a zero address");
    require(isOperatorControlled(owner), "ERC721: owner not under operator control");
    _operatorEnabled[owner] = false;
    return true;
  }
  
  function isOperatorControlled(address owner) public view returns (bool) {
    require(owner != address(0), "ERC721: owner is a zero address");
    return _operatorEnabled[owner];
  }

  function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, AccessControl) returns (bool) {
      return super.supportsInterface(interfaceId);
  }

  function msgSender() internal view returns(address sender) {
    if(msg.sender == _forwarder) {
      bytes memory array = msg.data;
      uint256 index = msg.data.length;
      assembly {
          // Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those.
          sender := and(mload(add(array, index)), 0xffffffffffffffffffffffffffffffffffffffff)
      }
    } else {
        sender = msg.sender;
    }
    return sender;
  }

  function setForwarder(address forwarder) external onlyRole(OPERATOR_ROLE) returns (bool) {
    _forwarder = forwarder;
    return true;
  }
  
  function getForwarder() external view returns (address) {
    return _forwarder;
  }
}

I don't understand, is it an off-chain database that you're talking about? What kind of data is in there?

If you need to migrate data to a new smart contract you will need to add a special-purpose function to the contract that allows you to load the data into it.

1 Like

Sorry, yes its an off-chain database but the database contains info relating to the smart contract i.e names, amount of tokens held by individuals, their tokenURI's, account records etc..

The database talks to the smart contract when data is inputted, bit like a dapp?

Correct me if i am wrong, but would i need to create a separate smart contract which can retrieve and store this data, which acts as a middle-man or abstract contract on behalf of the new smart contract, which would import this data into its storage through inheritance?

The new smart contract will be upgradeable and contain an initializer function, so i presume the data storage would sit within this function?

Thanks

1 Like

In your new ERC721 contract, design an initialization function (possibly in the constructor) that takes the relevant data from the old contract and initializes the new contract's state. Use the transferFrom function of the old ERC721 contract to transfer ownership of each token to the new contract. This may require permission from the old contract's owner or an appropriate role. For each token transferred, call the appropriate functions in the new contract to set metadata like token URIs, digests, or any other information associated with the tokens. After the migration, verify that the lead data enrichment in the new contract matches the data in the old contract. You may want to write additional tests to confirm the integrity of the migrated data.