KoreanFoodie's Study

Solidity 튜토리얼 #2 : 기본 문법 추가 본문

Tutorials/Solidity

Solidity 튜토리얼 #2 : 기본 문법 추가

GoldGiver 2022. 9. 21. 11:42

크립토 좀비에서 제공하는 튜토리얼을 통해 배우는 Solidity 문법을 정리하고 있습니다!

이전 게시글에서 작성했던 코드를 조금 확장해서, 좀비를 생성하는 것 말고도 다른 여러 가지 기능을 추가적으로 구현했다. 다른 개체를 먹고 다른 개체의 dna 와 섞인 새로운 좀비를 생성하거나, cryptokitty 의 dna 를 불러와서 좀비를 만들어보는 등의 함수가 구현되어 있다.

편의를 위해, 코드를 zombiefactory.sol 과 zombiefeeding.sol 로 나누었다.

 

이번 장을 정리한 내용은 다음과 같다.

1. Ethereum blockchaing 은 accounts 로 이루어져 있고, 각 accounts 에는 address 가 있다.

 

2. mapping 을 통해 C++ 에서의 map 같은 기능을 활용할 수 있다.

mapping (address => uint) public accountbalance;

 

3. msg.sender 라는, 모든 함수에서 접근 가능한 전역 변수가 있다.

msg.sender 는 해당 함수를 호출하는 사람(혹은 smart contract)의 address 를 나타낸다.

 

4. require 문을 통해, C++ 에서의 assert 같은 기능을 구현할 수 있다.

// Solidity 는 string 비교가 안되어 hash 값을 비교한다
require(keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked("Vitalik")));

 

5. 상속을 통해 기반 클래스의 변수와 메서드를 활용할 수 있다.

contract Doge {
  function catchphrase() public returns (string memory) {
    return "So Wow CryptoDoge";
  }
}

contract BabyDoge is Doge {
  function anotherCatchphrase() public returns (string memory) {
    return "Such Moon BabyDoge";
  }
}

 

6. import 를 이용해 다른 .sol 파일을 불러올 수 있다.

import "./someothercontract.sol";

 

7. 변수를 저장하는 장소는 storage 와 memory 가 있다.

storage 는 블록체인에 영구히 저장되고, memory 는 임시적으로 생성되는 지역 변수 같은 개념이다. hard disk 와 ram 의 차이라고 보면 편하다.

일반적으로 solidity 가 알아서 storage 인지 memory 인지 구분을 해 주지만, 해당 키워드를 사용해야 할 때도 있다.

contract SandwichFactory {
  struct Sandwich {
    string name;
    string status;
  }

  Sandwich[] sandwiches;

  function eatSandwich(uint _index) public {
    // Sandwich mySandwich = sandwiches[_index];

    // ^ Seems pretty straightforward, but solidity will give you a warning
    // telling you that you should explicitly declare `storage` or `memory` here.

    // So instead, you should declare with the `storage` keyword, like:
    Sandwich storage mySandwich = sandwiches[_index];
    // ...in which case `mySandwich` is a pointer to `sandwiches[_index]`
    // in storage, and...
    mySandwich.status = "Eaten!";
    // ...this will permanently change `sandwiches[_index]` on the blockchain.

    // If you just want a copy, you can use `memory`:
    Sandwich memory anotherSandwich = sandwiches[_index + 1];
    // ...in which case `anotherSandwich` will simply be a copy of the 
    // data in memory, and...
    anotherSandwich.status = "Eaten!";
    // ...will just modify the temporary variable and have no effect 
    // on `sandwiches[_index + 1]`. But you can do this:
    sandwiches[_index + 1] = anotherSandwich;
    // ...if you want to copy the changes back into blockchain storage.
  }
}

 

8. 함수 접근 지정자에는 public 과 private 말고도, 추가적으로 interanl 과 external 이 존재한다.

  • internal : private 과 같지만, 해당 contract 를 상속받은 contract 에서도 접근 가능
  • external : public 과 같지만, 해당 contract '외부에서만' 호출 가능

 

9. Solidity 에서 interface 는 다음과 같이 사용한다.

// 인터페이스 정의
contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

// 정의한 인터페이스를 실제 contract 의 주소와 연결해서 사용한다
contract MyContract {
  address NumberInterfaceAddress = 0xab38... 
  // ^ The address of the FavoriteNumber contract on Ethereum
  NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
  // Now `numberContract` is pointing to the other contract

  function someFunction() public {
    // Now we can call `getNum` from that contract:
    uint num = numberContract.getNum(msg.sender);
    // ...and do something with `num` here
  }
}

 

10. Solidity 는 여러 개의 변수를 동시에 return 할 수 있다.

function multipleReturns() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

function processMultipleReturns() external {
  uint a;
  uint b;
  uint c;
  // This is how you do multiple assignment:
  (a, b, c) = multipleReturns();
}

// Or if we only cared about one of the values:
function getLastReturnValue() external {
  uint c;
  // We can just leave the other fields blank:
  (,,c) = multipleReturns();
}

 

 

 

위의 내용을 종합한 전체 코드는 다음과 같다!

 

zombiefactory.sol

 

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string memory _name, uint _dna) internal {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string memory _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    function createRandomZombie(string memory _name) public {
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        randDna = randDna - randDna % 100;
        _createZombie(_name, randDna);
    }

}

 

zombiefeeding.sol

pragma solidity >=0.5.0 <0.6.0;

import "./zombiefactory.sol";

contract KittyInterface {
  function getKitty(uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
  );
}

contract ZombieFeeding is ZombieFactory {

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  KittyInterface kittyContract = KittyInterface(ckAddress);

  // Modify function definition here:
  function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    // Add an if statement here
      if (keccak256(abi.encodePacked(_species)) == keccak256(abi.encodePacked("kitty"))) {
        newDna = newDna - newDna % 100 + 99;
      }
    _createZombie("NoName", newDna);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    // And modify function call here:
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }

}
 
Comments