4. Write Klaystagram Smart Contract

  1. Background

  2. Contract setup

  3. Set events and data structure

  4. Write functions 4.1. uploadPhoto 4.2. transferOwnership 4.3. getPhoto

1) Background

We will make a simple contract called "Klaystagram".

  • PhotoData struct is defined to store various photo data.

  • User can upload photo and transfer the ownership photo via uploadPhoto and transferOwnership functions.

2) 스마트 컨트랙트 설정

  • Specify solidity version. We recommend using 0.5.6 stable version.

  • We will make use of ERC721 standard to build non-fungible tokens.

    • Import ERC721.sol and ERC721Enumerable.sol

    • Check out detailed information about ERC721 at erc721.org

pragma solidity 0.5.6;

import "./ERC721/ERC721.sol";
import "./ERC721/ERC721Enumerable.sol";

contract Klaystagram is ERC721, ERC721Enumerable {

3) 이벤트 및 데이터 구조 설정

We need to set up an event to keep track of activities on blockchain.

As for data structure, mapping _photoList takes a uint256 tokenId to map a specific PhotoData struct. By defining PhotoUploaded event, transaction receipt will log this event whenever function containing this is called.

event PhotoUploaded (uint indexed tokenId, bytes photo, string title, string location, string description, uint256 timestamp);

mapping (uint256 => PhotoData) private _photoList;

struct PhotoData {
    uint256 tokenId;                       // 1부터 차례로 1씩 증가하는 고유한 토큰 ID
    address[] ownerHistory;                // 이전 소유자들의 기록
    bytes photo;                           // 이미지의 소스
    string title;                          // 사진 제목
    string location;                       // 사진 촬영 장소
    string description;                    // 사진에 대한 간략한 설명
    uint256 timestamp;                     // 업로드된 시간
}

4) 함수 작성

Let's write some functions that interact with the contract. In this tutorial let us only consider two functions: uploadPhoto and transferOwnership. Check out Klaystagram.sol to see the whole set of functions.

4-1) uploadPhoto

uploadPhoto function takes 4 arguments including photo's image source. To keep things simple, tokenId will start from 1 and will increase by 1.

_mint function is from ERC721 contract. It creates a new token and assign it to a specific address, which in this case, msg.sender. In this application, logged in user will create transaction with their own private key. So msg.sender will be the user's public address.

Finally, initialize PhotoData struct, locate it inside _photoList mapping, and push the owner address into ownerHistory array. And don't forget to emit the event we just created. As mentioned above, this event will be included in transaction receipt.

function uploadPhoto(bytes memory photo, string memory title, string memory location, string memory description) public {
    uint256 tokenId = totalSupply() + 1;

    _mint(msg.sender, tokenId);

    address[] memory ownerHistory;

    PhotoData memory newPhotoData = PhotoData({
        tokenId : tokenId,
        ownerHistory : ownerHistory,
        photo : photo,
        title: title,
        location : location,
        description : description,
        timestamp : now
    });

    _photoList[tokenId] = newPhotoData;
    _photoList[tokenId].ownerHistory.push(msg.sender);

    emit PhotoUploaded(tokenId, photo, title, location, description, now);
}

4-2) transferOwnership

Let's take a look at transferOwnership function. When transferring photo ownership, we need to do two things. First, we have to reassign the owner, and then we have to push new owner address into ownerHistory array.

To do this, transferOwnership first calls safeTransferFrom function from ERC721 standard, which eventually calls transferFrom function. As mentioned above, right after token transfer is successfully done, we have to push new owner information into ownerHistory array, and that is exactly why transferFrom is overridden as below.

/**
  * @notice safeTransferFrom 함수는 수신자가 ERC721 토큰을 처리할 수 있는지 체크하여 토큰이 유실되는 상황을 줄여줍니다. After checking is done, it will call transferFrom function defined below
  */
function transferOwnership(uint256 tokenId, address to) public returns(uint, address, address, address) {
    safeTransferFrom(msg.sender, to, tokenId);
    uint ownerHistoryLength = _photoList[tokenId].ownerHistory.length;
    return (
        _photoList[tokenId].tokenId,
        //original owner
        _photoList[tokenId].ownerHistory[0],
        //previous owner, length cannot be less than 2
        _photoList[tokenId].ownerHistory[ownerHistoryLength-2],
        //current owner
        _photoList[tokenId].ownerHistory[ownerHistoryLength-1]);
}

/**
  * @notice Recommend using transferOwnership, which uses safeTransferFrom function
  * @dev Override transferFrom function to make sure that every time ownership transfers
  *  new owner address gets pushed into ownerHistory array
  */
function transferFrom(address from, address to, uint256 tokenId) public {
    super.transferFrom(from, to, tokenId);
    _photoList[tokenId].ownerHistory.push(to);
}

4-3) getPhoto

Finally, let's make a getter function that fetches data stored in the smart contract. By calling a single function, we want to fetch every information regarding a specific photo. So getPhoto function takes an index(token id) as an argument and returns every element in PhotoData struct.

function getPhoto(uint tokenId) public view
returns(uint256, address[] memory, bytes memory, string memory, string memory, string memory, uint256) {
    require(_photoList[tokenId].tokenId != 0, "Photo does not exist");
    return (
        _photoList[tokenId].tokenId,
        _photoList[tokenId].ownerHistory,
        _photoList[tokenId].photo,
        _photoList[tokenId].title,
        _photoList[tokenId].location,
        _photoList[tokenId].description,
        _photoList[tokenId].timestamp);
}

This is it, now we can deploy this contract!

Last updated