[Solidity] CryptoZombie

yooniΒ·2022λ…„ 4μ›” 2일
0
post-thumbnail

πŸ§Ÿβ€β™€οΈ https://cryptozombies.io/ko/

ν˜„μž¬, 솔리디티λ₯Ό μ΅νžˆκΈ°μ— κ°€μž₯ 쒋은 ν•™μŠ΅ 도ꡬ(?)λŠ” 크립토 쒀비인 것 κ°™λ‹€. 크립토 μ’€λΉ„μ˜ 첫 번째 μ½”μŠ€μΈ Basic solidity μ½”μŠ€μ—μ„œ 기얡해두어야 ν•  κ°œλ…λ“€μ„ κΈ°λ‘ν•΄λ‚˜κ°€λ €κ³  ν•œλ‹€.




πŸ§Ÿβ€β™‚οΈ Lesson 1


🦠 μƒνƒœ λ³€μˆ˜

μƒνƒœ λ³€μˆ˜λŠ” μ»¨νŠΈλž™νŠΈ μ €μž₯μ†Œ 즉, 이더리움 블둝체인에 영ꡬ적으둜 μ €μž₯λœλ‹€.



🦠 ꡬ쑰체 동적 λ°°μ—΄

ꡬ쑰체의 동적 배열을 μƒμ„±ν•˜μ—¬ μƒνƒœ λ³€μˆ˜λ‘œ ν™œμš©ν•˜λ©΄, 마치 λ°μ΄ν„°λ² μ΄μŠ€μ²˜λŸΌ κ΅¬μ‘°ν™”λœ 데이터λ₯Ό μ €μž₯ν•  수 μžˆλ‹€.

Person[] people;



🦠 _ μ–Έλ”μŠ€μ½”μ–΄ 넀이밍 κ΄€λ‘€λ“€

  • ν•¨μˆ˜ 인자λͺ…은 μ–Έλ”μŠ€μ½”μ–΄(_) 둜 μ‹œμž‘ν•˜μ—¬ μ „μ—­ λ³€μˆ˜μ™€ κ΅¬λΆ„ν•œλ‹€.
  • private ν•¨μˆ˜μ˜ ν•¨μˆ˜λͺ…은 μ–Έλ”μŠ€μ½”μ–΄(_) 둜 μ‹œμž‘ν•œλ‹€.
function _myFunc(string _name, uint _age) private { ... }



🦠 이벀트

ν”„λ‘ νŠΈ μ•±μ—μ„œ νŠΉμ • μ•‘μ…˜μ— λŒ€ν•œ μ΄λ²€νŠΈμ— λŒ€ν•΄ κ·€λ₯Ό κΈ°μšΈμΈλ‹€. μ΄λ²€νŠΈκ°€ μ‹€ν–‰λ˜λ©΄ 앱에 ν•¨μˆ˜κ°€ μ‹€ν–‰λ˜μ—ˆμŒμ„ μ•Œλ¦΄ 수 μžˆλ‹€.

// μ»¨νŠΈλž™νŠΈμ˜ solidity μ½”λ“œ

event IntegersAdded(uint x, uint y, uint result);

function add(uint _x, uint _y) public returns(uint) {
	uint result = _x + _y;
    IntegersAdded(_x, _y, result) // 이벀트λ₯Ό μ‹€ν–‰ν•˜μ—¬ add ν•¨μˆ˜μ˜ 싀행을 μ•Œλ¦Ό
    return result;
// ν”„λ‘ νŠΈ μ•±μ˜ Javascript μ½”λ“œ

YourContract.IntegersAdded(function(error, result) {
	// 이벀트(ν•¨μˆ˜) μ‹€ν–‰κ³Ό κ΄€λ ¨λœ 행동에 λŒ€ν•œ μ½”λ“œ
});




πŸ§Ÿβ€β™‚οΈ Lesson 2


🦠 μΈν„°νŽ˜μ΄μŠ€

μ„œλ‘œ λ‹€λ₯Έ μ»¨νŠΈλž™νŠΈ κ°„μ˜ μƒν˜Έμž‘μš©μ„ μœ„ν•¨

μ–΄λ–€ μ™ΈλΆ€ μ»¨νŠΈλž™νŠΈμ˜ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ½”λ“œμ— ν¬ν•¨ν•˜λ©΄, ν•΄λ‹Ή μ»¨νŠΈλž™νŠΈμ— λŒ€ν•΄ μ•Œ 수 있게 λœλ‹€. κ΅¬μ²΄μ μœΌλ‘œλŠ” ν•΄λ‹Ή μ»¨νŠΈλž™νŠΈμ— μ •μ˜λœ ν•¨μˆ˜μ˜ νŠΉμ„±, 호좜 방법, μ˜ˆμƒλ˜λŠ” 응닡 λ‚΄μš© 등에 λŒ€ν•΄ μ•Œ 수 μžˆλ‹€.

즉 μ™ΈλΆ€ μ»¨νŠΈλž™νŠΈλ₯Ό ν™œμš©ν•œ μ½”λ“œλ₯Ό μž‘μ„±ν•  λ•Œμ—λŠ”, μ™ΈλΆ€ μ»¨νŠΈλž™νŠΈμ˜ μ£Όμ†Œλ§Œ κ°€μ Έμ˜€λŠ”κ²ƒμ΄ μ•„λ‹ˆλΌ ν•΄λ‹Ή μ»¨νŠΈλž™νŠΈμ˜ (μƒν˜Έμž‘μš© ν•˜κ³ μž ν•˜λŠ” λ‚΄μš©μ΄ ν¬ν•¨λ˜μ–΄ μžˆλŠ”) μΈν„°νŽ˜μ΄μŠ€λ₯Ό κΈ°μž¬ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€λŠ” 것이닀. 블둝체인 μƒμ˜ μ΄μš©ν•˜κ³ μž ν•˜λŠ” μ™ΈλΆ€ μ»¨νŠΈλž™νŠΈμ— μ£Όμ†Œλ‘œ μ ‘κ·Όν•˜κ³  μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ”Œμ›Œ 솔리디티 μ½”λ“œμ—μ„œ 이용 κ°€λŠ₯ν•œ ν˜•νƒœλ‘œ μ‚¬μš©ν•˜κ³ μž 함이닀. μ‰½κ²Œ 말해 'λ‚΄ μ»¨νŠΈλž™νŠΈ' μ•ˆμ— 'μ™ΈλΆ€ μ»¨νŠΈλž™νŠΈμ˜ μΈν„°νŽ˜μ΄μŠ€'λ₯Ό μž‘μ„±ν•˜λŠ” 것이닀.

// zombiefeeding.sol

pragma solidity ^0.4.19;

// 블둝체인에 κ³΅κ°œλ˜μ–΄ μžˆλŠ” 크립토 ν‚€ν‹° μ½”λ“œλ₯Ό ν™œμš©ν•˜μ—¬ μΈν„°νŽ˜μ΄μŠ€ μž‘μ„±
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;
  // λ‚΄ μ½”λ“œμ—μ„œ 이용 κ°€λŠ₯ν•œ kittyContract둜 λ§Œλ“€μ–΄μ£ΌκΈ°
  KittyInterface kittyContract = KittyInterface(ckAddress);
  ...
}



🦠 상속과 컴파일

μ»¨νŠΈλž™νŠΈλ₯Ό 배포할 μ€€λΉ„κ°€ 되면, μ΅œμ’…μ μœΌλ‘œ 상속을 λ°›λŠ” κ°€μž₯ λ§ˆμ§€λ§‰ μžμ†μ— ν•΄λ‹Ήν•˜λŠ” μ»¨νŠΈλž™νŠΈλ§Œ μ»΄νŒŒμΌν•˜μ—¬ λ°°ν¬ν•˜λ©΄ μƒμœ„ 상속받고 μžˆλŠ” μ»¨νŠΈλž™νŠΈλ“€κΉŒμ§€ 접근이 κ°€λŠ₯ν•˜λ‹€.




πŸ§Ÿβ€β™‚οΈ Lesson 3


🦠 μƒμ„±μž

μ»¨νŠΈλž™νŠΈκ°€ 처음 생성될 λ•Œ ν˜Ήμ€ μΈμŠ€ν„΄μŠ€ν™” 될 λ•Œ, λ³€μˆ˜μ˜ 값을 μ›ν•˜λŠ” κ°’μœΌλ‘œ μ΄ˆκΈ°ν™”ν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•œλ‹€. μ»¨νŠΈλž™νŠΈκ°€ 생성될 λ•Œ 단 ν•œλ²ˆ μž‘λ™ν•œλ‹€. μƒμ„±μžλ₯Ό payableν•˜κ²Œ μž‘μ„±ν•˜λ©΄ μ»¨νŠΈλž™νŠΈ μƒμ„±μ‹œ μ»¨νŠΈλž™νŠΈ 계정에 이더λ₯Ό 보낼 수 μžˆλ‹€.

contract example {
  // μƒνƒœ λ³€μˆ˜ μ„ μ–Έ
  string public name;
  uint public age;
  // μƒμ„±μžλ₯Ό 톡해 μƒνƒœ λ³€μˆ˜ μ΄ˆκΈ°ν™”
  constructor(string memory _name, uint _age) {
    name = _name;
    age = _age;
  }
}



🦠 κ°€μŠ€ μ ˆμ•½μ„ μœ„ν•œ ꡬ쑰체 μ••μΆ•

uintλŠ” uint256 외에도 uint8, uint16, uint32 λ“±μ˜ ν•˜μœ„ νƒ€μž…λ“€μ΄ μžˆμ§€λ§Œ νŠΉλ³„ν•œ μ΄μœ κ°€ μ—†λŠ” ν•œ 이런 ν•˜μœ„ νƒ€μž…μ„ μ‚¬μš©ν•˜λŠ” 것은 μ•„λ¬΄λŸ° 이득이 μ—†λ‹€. μ†”λ¦¬λ””ν‹°μ—μ„œλŠ” uint의 크기에 상관없이 256λΉ„νŠΈμ˜ μ €μž₯ 곡간을 미리 μž‘μ•„λ†“κΈ° λ•Œλ¬Έμ΄λ‹€. uint256(uint) λŒ€μ‹  uint8을 μ‚¬μš©ν•œλ‹€κ³  ν•΄μ„œ κ°€μŠ€ μ†Œλͺ¨λŠ” μ „ν˜€ 쀄어듀지 μ•ŠλŠ”λ‹€.

ν•˜μ§€λ§Œ, ꡬ쑰체 μ•ˆμ—μ„œλŠ” λ‹€λ₯΄λ‹€. ꡬ쑰체 μ•ˆμ— μ—¬λŸ¬ 개의 uintλ₯Ό λ§Œλ“ λ‹€λ©΄ κ°€λŠ₯ν•œ 더 μž‘μ€ 크기의 uintλ₯Ό μ“°λŠ” 것이 μ’‹λ‹€. μ†”λ¦¬λ””ν‹°μ—μ„œ κ·Έ λ³€μˆ˜λ“€μ„ 더 적은 곡간을 μ°¨μ§€ν•˜λ„λ‘ μ••μΆ•ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€. ꡬ쑰체 μ•ˆμ—μ„œλŠ” κ°€λŠ₯ν•œ ν•œ μž‘μ€ 크기의 μ •μˆ˜ νƒ€μž…μ„ 쓰도둝 ν•˜μž. λ˜ν•œ λ™μΌν•œ 데이터 νƒ€μž…μ€ ν•˜λ‚˜λ‘œ λ¬Άμ–΄μ•Ό μ €μž₯ 곡간을 μ΅œμ†Œν™”ν•  수 μžˆλ‹€.

μ•„λž˜μ˜ μ½”λ“œμ—μ„œ NormalStruct λ³΄λ‹€λŠ” MiniMe μ»¨νŠΈλž™νŠΈκ°€, MiniMe λ³΄λ‹€λŠ” MiniMe2 μ»¨νŠΈλž™νŠΈκ°€ 더 적은 μ €μž₯ 곡간을 μ°¨μ§€ν•œλ‹€.

struct NormalStruct {
  uint a;
  uint b;
  uint c;
}

struct MiniMe {
  uint32 a;
  uint b;
  uint32 c;
}

struct MiniMe2 {
  uint32 a;
  uint32 c;
  uint b;
}



🦠 μ‹œκ°„ λ‹¨μœ„

now : ν˜„μž¬μ˜ μœ λ‹‰μŠ€ νƒ€μž„ μŠ€νƒ¬ν”„(1970λ…„ 1μ›” 1일뢀터 μ§€κΈˆκΉŒμ§€μ΄ 초 λ‹¨μœ„ ν•©)

μœ λ‹‰μŠ€ νƒ€μž„μ€ 32λΉ„νŠΈ 숫자둜 μ €μž₯λœλ‹€. μœ λ‹‰μŠ€ νƒ€μž„ μŠ€νƒ¬ν”„ 값이 32λΉ„νŠΈλ‘œ ν‘œμ‹œκ°€ λ˜μ§€ μ•Šμ„ 만큼 컀지면 "Year 2038" λ¬Έμ œκ°€ λ°œμƒν•  것이닀. ν•˜μ§€λ§Œ nowλŠ” 기본적으둜 uint256을 λ°˜ν™˜ν•˜κΈ° λ•Œλ¬Έμ— λͺ…μ‹œμ μœΌλ‘œ uint32(now)둜 λ³€ν™˜ν•  ν•„μš”κ°€ μžˆλ‹€.

μ†”λ¦¬λ””ν‹°λŠ” λ˜ν•œ seconds, minutes, hours, days, weeks, years와 같은 μ‹œκ°„ λ‹¨μœ„ λ˜ν•œ μ œκ³΅ν•˜λ©°, 이듀은 그에 ν•΄λ‹Ήν•˜λŠ” 길이 만큼의 초 λ‹¨μœ„ uint 숫자둜 λ³€ν™˜λœλ‹€. 1 daysλŠ” 86400초λ₯Ό μ˜λ―Έν•œλ‹€.

❗️ ν˜„μž¬λŠ” now λŒ€μ‹  block.timestamp둜 λŒ€μ²΄λ˜μ—ˆλ‹€.



🦠 ꡬ쑰체λ₯Ό ν•¨μˆ˜μ˜ 인자둜 μ „λ‹¬ν•˜κΈ°

private λ˜λŠ” internal둜 μ •μ˜λœ ν•¨μˆ˜μ˜ 인자둜 ꡬ쑰체의 storage 포인터λ₯Ό 전달할 수 μžˆλ‹€. ꡬ쑰체λ₯Ό 식별할 id 등을 μ „λ‹¬ν•˜λŠ” 것이 μ•„λ‹ˆλΌ ꡬ쑰체에 λŒ€ν•œ μ°Έμ‘°λ₯Ό μ „λ‹¬ν•˜λŠ” 것이닀.

contract ZombieFeeding {
  
  // ꡬ쑰체
  struct Zombie {
    string name;
    uint dna;
    uint32 level;
    uint32 readyTime;
  }
  
  // _isReady ν•¨μˆ˜μ˜ 인자둜 Zombie ꡬ쑰체의 storage 포인터λ₯Ό μ „λ‹¬ν•˜μ—¬ μ°Έμ‘°ν•˜κ²Œ ν•œλ‹€.
  function _isReady(Zombie storage _zombie) internal view returns(bool) {
    return (_zombie.readyTime <= now);
  }
  
  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal {
    ...
    
    // Zombie ꡬ쑰체 storage 포인터 μ„ μ–Έ 및 ν• λ‹Ή
    Zombie storage myZombie = zombies[_zombieId];
    
    // Zombie ꡬ쑰체λ₯Ό 인자둜 μ „λ‹¬ν•˜μ—¬ _isReady μ‹€ν–‰
    require(_isReady(myZombie));
    
    ...
  }
}



🦠 κ°€μŠ€λ₯Ό μ†Œλͺ¨ν•˜μ§€ μ•ŠλŠ” view ν•¨μˆ˜

view둜 μ •μ˜λœ ν•¨μˆ˜λŠ” μ™ΈλΆ€μ—μ„œ ν˜ΈμΆœλ˜μ—ˆμ„ λ•Œ κ°€μŠ€λ₯Ό μ „ν˜€ μ†Œλͺ¨ν•˜μ§€ μ•ŠλŠ”λ‹€. 블둝체인에 μ–΄λ– ν•œ νŠΈλžœμž­μ…˜λ„ μƒμ„±ν•˜μ§€ μ•Šκ³  단지 확인을 μœ„ν•œ 질의만 날리기 λ•Œλ¬Έμ΄λ‹€.

μ£Όμ˜ν•  것은, view ν•¨μˆ˜κ°€ μ™ΈλΆ€κ°€ μ•„λ‹Œ 동일 μ»¨νŠΈλž™νŠΈ λ‚΄μ˜ λ‹€λ₯Έ ν•¨μˆ˜μ—μ„œ λ‚΄λΆ€μ μœΌλ‘œ 호좜될 κ²½μš°μ—λŠ” κ°€μŠ€λ₯Ό μ†Œλͺ¨ν•œλ‹€λŠ” 점이닀. 이것은 λ‹€λ₯Έ ν•¨μˆ˜κ°€ 이더리움에 νŠΈλžœμž­μ…˜μ„ μƒμ„±ν•˜κ³  μ΄λŠ” λͺ¨λ“  κ°œλ³„ λ…Έλ“œμ—μ„œ κ²€μ¦λ˜μ–΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€. view ν•¨μˆ˜λŠ” μ™ΈλΆ€μ—μ„œ ν˜ΈμΆœλ˜μ—ˆμ„ λ•Œλ§Œ λ¬΄λ£Œμ΄λ‹€.

pure ν•¨μˆ˜ μ—­μ‹œ λ§ˆμ°¬κ°€μ§€λ‘œ μ™ΈλΆ€μ—μ„œ ν˜ΈμΆœλ˜μ—ˆμ„ λ•Œ κ°€μŠ€λ₯Ό μ†Œλͺ¨ν•˜μ§€ μ•Šκ³ , λ‚΄λΆ€μ—μ„œ ν˜ΈμΆœλ˜μ—ˆμ„ λ•Œμ—λŠ” κ°€μŠ€λ₯Ό μ†Œλͺ¨ν•œλ‹€.



🦠 memory에 λ°°μ—΄ μ„ μ–Έ

storageλ₯Ό μ‚¬μš©ν•˜λŠ” 것은 λΉ„μ‹Έλ‹€. ν•¨μˆ˜κ°€ 싀행될 λ™μ•ˆμ—λ§Œ ν•„μš”ν•œ 배열은 memory에 μ„ μ–Έν•΄μ£ΌλŠ” 편이 λ‚«λ‹€. μ΄λŠ” storage의 배열을 직접 μ—…λ°μ΄νŠΈν•˜λŠ” 것보닀 훨씬 μ €λ ΄ν•˜λ‹€.

memory 배열은 λ°˜λ“œμ‹œ 길이 μΈμˆ˜μ™€ ν•¨κ»˜ μƒμ„±λ˜μ–΄μ•Ό ν•œλ‹€. storage 배열은 array.push()둜 크기가 μžλ™μœΌλ‘œ μ‘°μ ˆλ˜μ§€λ§Œ memory 배열은 그렇지 μ•Šλ‹€.

function getArray() external pure returns(uint[]) {
  
  // λ©”λͺ¨λ¦¬μ— 길이 3의 μƒˆλ‘œμš΄ 배열을 μƒμ„±ν•œλ‹€. 이 배열은 ν•¨μˆ˜κ°€ μ‹€ν–‰λ˜λŠ” λ™μ•ˆμ—λ§Œ μ‘΄μž¬ν•  것이닀.
  uint[] memory values = new uint[](3);
  
  // 여기에 νŠΉμ •ν•œ 값듀을 λ„£λŠ”λ‹€.
  values.push(1);
  values.push(2);
  values.push(3);
  
  // ν•΄λ‹Ή 배열을 λ°˜ν™˜ν•œλ‹€.
  return values;
}




πŸ§Ÿβ€β™‚οΈ Lesson 4


🦠 keccak256

μ†”λ¦¬λ””ν‹°μ—μ„œ keccak256은 크게 두 가지 λͺ©μ μœΌλ‘œ ν™œμš©λœλ‹€.

(1) string을 비ꡐ할 λ•Œ

μ†”λ¦¬λ””ν‹°μ—λŠ” λ¬Έμžμ—΄μ„ λΉ„κ΅ν•˜λŠ” ν•¨μˆ˜κ°€ λ‚΄μž₯λ˜μ–΄ μžˆμ§€ μ•Šλ‹€. λ”°λΌμ„œ λ¬Έμžμ—΄μ„ λΉ„κ΅ν•˜λŠ” 것이 μ•„λ‹ˆλΌ κ·Έ λ¬Έμžμ—΄μ„ ν•΄μ‹œν™”ν•œ 값을 λΉ„κ΅ν•œλ‹€.

function stringCompare(string _a, string _b) public returns (bool) {
  return (keccak256(_a) == keccak256(_b));
}

(2) λ‚œμˆ˜λ₯Ό 생성할 λ•Œ (μ•ˆμ „ν•˜μ§€λŠ” μ•Šμ€ 방법)

μ•ˆμ „ν•˜κ²Œ λ‚œμˆ˜λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄μ„œλŠ” oracle(이더리움 μ™ΈλΆ€μ—μ„œ 데이터λ₯Ό λ°›μ•„μ˜€λŠ” μ•ˆμ „ν•œ 방법 쀑 ν•˜λ‚˜)을 μ‚¬μš©ν•  수 μžˆλ‹€.

μ•„λž˜ μ˜ˆμ‹œμ—μ„œλŠ” now의 νƒ€μž„μŠ€νƒ¬ν”„ κ°’, msg.sender, μ¦κ°€ν•˜λŠ” nonceλ₯Ό μž…λ ₯λ°›μ•„ μž„μ˜μ˜ ν•΄μ‹œ 값을 μ–»κ³  이λ₯Ό 100으둜 λ‚˜λˆˆ λ‚˜λ¨Έμ§€λ₯Ό ꡬ해 0~99 μ‚¬μ΄μ˜ λ‚œμˆ˜λ₯Ό 얻을 수 μžˆλ‹€.

uint randNonce = 0;
uint random = uint(keccak256(now, msg.sender, randNonce)) % 100;
randNonce++;
uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100;




πŸ§Ÿβ€β™‚οΈ Lesson 5


🦠 라이브러리

λΌμ΄λΈŒλŸ¬λ¦¬λŠ” νŠΉλ³„ν•œ μ’…λ₯˜μ˜ μ»¨νŠΈλž™νŠΈμ΄λ‹€. 라이브러리λ₯Ό 톡해 κΈ°λ³Έ 데이터 νƒ€μž…μ— ν•¨μˆ˜λ₯Ό 뢙일 수 μžˆλ‹€. using ꡬ문으둜 μ‚¬μš©ν•œλ‹€. uint λ°μ΄ν„°μ˜ μ˜€λ²„ν”Œλ‘œμš°/μ–Έλ”ν”Œλ‘œμš°λ₯Ό 막기 μœ„ν•΄ μ‚¬μš©ν•˜λŠ” SafeMath λΌμ΄λΈŒλŸ¬λ¦¬κ°€ λŒ€ν‘œμ μ΄λ‹€.

μ—¬λŸ¬ μ»¨νŠΈλž™νŠΈμ—μ„œ 자주 μ‚¬μš©λ˜λŠ” μ½”λ“œλ₯Ό λ”°λ‘œ 라이브러리둜 λ°°ν¬ν•˜λ©΄ μ»¨νŠΈλž™νŠΈλ§ˆλ‹€ 라이브러리λ₯Ό 적용만 ν•˜λ©΄ μ‰½κ²Œ μ½”λ“œλ₯Ό μ‚¬μš©ν•  수 있으며 맀번 μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” 것에 λΉ„ν•΄ κ°€μŠ€ μ†ŒλΉ„λŸ‰λ„ 적어진닀.

λΌμ΄λΈŒλŸ¬λ¦¬μ—λŠ” fallback ν•¨μˆ˜λ₯Ό μ •μ˜ν•  수 μ—†κΈ° λ•Œλ¬Έμ—, 이더λ₯Ό 받을 수 μ—†κ³  내뢀에 payableν•œ ν•¨μˆ˜λ„ μ •μ˜ν•  수 μ—†λ‹€. λ˜ν•œ 상속이 λΆˆκ°€λŠ₯ν•˜λ‹€. 계약 μƒνƒœλ₯Ό λ³€κ²½ν•˜κΈ° μœ„ν•œ 것이 μ•„λ‹Œ, μž…λ ₯ 및 λ°˜ν™˜ κ²°κ³Όλ₯Ό 기반으둜 κ°„λ‹¨ν•œ μž‘μ—…μ„ μˆ˜ν–‰ν•˜λŠ” 데 μ‚¬μš©λœλ‹€.

library SafeMath {

  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

SafeMath 라이브러리λ₯Ό μ‚¬μš©ν•˜κ³ μž ν•˜λŠ” μ»¨νŠΈλž™νŠΈ λ‚΄λΆ€μ—μ„œ, using SafeMath for uint256; 라고 써주면 SafeMath λ‚΄μ˜ λ©”μ†Œλ“œλ“€μ΄ uint256 데이터 νƒ€μž…μ— λΆ™κ²Œ λœλ‹€.



🦠 require vs assert vs revert (μ—λŸ¬ ν•Έλ“€λŸ¬)

  • require
    νŠΉμ • 쑰건을 λ§Œμ‘±ν•˜μ§€ μ•ŠμœΌλ©΄ μ—λŸ¬λ₯Ό λ°œμƒμ‹œν‚¨λ‹€. μ—λŸ¬κ°€ λ°œμƒν•˜μ—¬ ν•¨μˆ˜ 싀행이 μ‹€νŒ¨ν•˜λ©΄ 남은 κ°€μŠ€λ₯Ό μ‚¬μš©μžμ—κ²Œ ν™˜λΆˆν•΄μ€€λ‹€.

  • assert
    νŠΉμ • 쑰건을 λ§Œμ‘±ν•˜μ§€ μ•ŠμœΌλ©΄ μ—λŸ¬λ₯Ό λ°œμƒμ‹œν‚¨λ‹€. μ—λŸ¬κ°€ λ°œμƒν•˜μ—¬ ν•¨μˆ˜ 싀행이 μ‹€νŒ¨ν•˜λ©΄ κ°€μŠ€λ₯Ό μ‚¬μš©μžμ—κ²Œ λŒλ €μ£Όμ§€ μ•Šκ³  λͺ¨λ‘ μ†ŒλΉ„ν•΄λ²„λ¦°λ‹€. assertλŠ” uint μ˜€λ²„ν”Œλ‘œμš°μ™€ 같이 일반적으둜 μ½”λ“œκ°€ μ‹¬κ°ν•˜κ²Œ 잘λͺ» μ‹€ν–‰λ˜λŠ” κ²½μš°μ— μ‚¬μš©ν•œλ‹€.

  • revert
    쑰건 없이 무쑰건 μ—λŸ¬λ₯Ό λ°œμƒμ‹œν‚€κ³  남은 κ°€μŠ€λ₯Ό μ‚¬μš©μžμ—κ²Œ ν™˜λΆˆν•΄μ€€λ‹€.



🦠 natspec - 솔리디티 주석 μž‘μ„± ν‘œμ€€

/// @title 타이틀
/// @author μž‘μ„±μž
/// @notice μ‚¬μš©μžμ—κ²Œ μ»¨νŠΈλž™νŠΈκ°€ 무엇을 ν•˜λŠ”μ§€ μ„€λͺ…
contract Math {
  /// @notice μ‚¬μš©μžμ—κ²Œ ν•¨μˆ˜κ°€ 무엇을 ν•˜λŠ”μ§€ μ„€λͺ…
  /// @param x ν•¨μˆ˜μ˜ 맀개 λ³€μˆ˜
  /// @param y ν•¨μˆ˜μ˜ 맀개 λ³€μˆ˜
  /// @return z ν•¨μˆ˜μ˜ 리턴값
  /// @dev κ°œλ°œμžμ—κ²Œ μΆ”κ°€ 정보λ₯Ό μ„€λͺ…
  function multiply(uint x, uint y) returns (uint z) {
    // 이것은 일반적인 μ£Όμ„μœΌλ‘œ, natspec에 ν¬ν•¨λ˜μ§€ μ•ŠλŠ”λ‹€.
    z = x * y;
  }
}

@title, @author, @notice, @param, @return, @devκ°€ μ‚¬μš©λ  수 있으며 λͺ¨λ‘ ν•„μˆ˜λŠ” μ•„λ‹ˆλ‹€. ν•˜μ§€λ§Œ 각 ν•¨μˆ˜μ— λŒ€ν•œ μ„€λͺ…μœΌλ‘œ @devλŠ” λ‚¨κΈ°λŠ” 것이 ꢌμž₯λœλ‹€.




πŸ§Ÿβ€β™‚οΈ Lesson 6 - web3 개발 흐름


슀마트 μ»¨νŠΈλž™νŠΈμ˜ ν•¨μˆ˜λ₯Ό μ‹€ν–‰ν•˜κ³ μž ν•œλ‹€λ©΄, 이더리움 λ„€νŠΈμ›Œν¬ μƒμ˜ λ…Έλ“œ 쀑 ν•˜λ‚˜μ— 질의λ₯Ό 보내야 ν•œλ‹€. λ…Έλ“œλ“€μ€ μ‚¬λžŒμ΄ 읽기 λΆˆνŽΈν•œ JSON-RPC둜만 μ†Œν†΅ν•  수 μžˆλ‹€.

web3
JSON-RPCλ₯Ό 직접 μ΄μš©ν•˜μ§€ μ•Šκ³  μžλ°”μŠ€ν¬λ¦½νŠΈ ν”„λ‘ νŠΈμ—”λ“œ μΈν„°νŽ˜μ΄μŠ€λ‘œ 이더리움 λ…Έλ“œλ“€κ³Ό μƒν˜Έμž‘μš© ν•  수 있게 ν•΄μ£ΌλŠ” 라이브러리


🦠 ν”„λ‘ νŠΈμ—”λ“œ ν”„λ‘œμ νŠΈμ— web3 라이브러리λ₯Ό μ„€μΉ˜ν•œλ‹€.

npm install web3



🦠 Web3 Providerλ₯Ό μ„€μ •ν•˜μ—¬ μ–΄λ–€ λ…Έλ“œμ™€ 톡신할 것인지 μ„€μ •ν•œλ‹€.

λ‚˜μ˜ DApp을 μœ„ν•œ λ…Έλ“œλ₯Ό λ”°λ‘œ μš΄μ˜ν•  ν•„μš”κ°€ 없이 Infura, λ©”νƒ€λ§ˆμŠ€ν¬μ˜ ν”„λ‘œλ°”μ΄λ”λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€. λ©”νƒ€λ§ˆμŠ€ν¬λŠ” web3λΌλŠ” μ „μ—­ μžλ°”μŠ€ν¬λ¦½νŠΈ 객체λ₯Ό 톡해 λΈŒλΌμš°μ €μ— Web3 ν”„λ‘œλ°”μ΄λ”λ₯Ό μ£Όμž…ν•œλ‹€.

window.addEventListener('load', function() {

  // Web3κ°€ λΈŒλΌμš°μ €μ— μ£Όμž…λ˜μ—ˆλŠ”μ§€ 확인(Mist/MetaMask)
  if (typeof web3 !== 'undefined') {
    // Mist/MetaMask의 ν”„λ‘œλ°”μ΄λ” μ‚¬μš©
    web3js = new Web3(web3.currentProvider);
  } else {
    // μ‚¬μš©μžκ°€ Metamaskλ₯Ό μ„€μΉ˜ν•˜μ§€ μ•Šμ€ κ²½μš°μ— λŒ€ν•΄ 처리
    // μ‚¬μš©μžλ“€μ—κ²Œ Metamaskλ₯Ό μ„€μΉ˜ν•˜λΌλŠ” λ“±μ˜ 메세지λ₯Ό 보여쀄 것
  }

  // 이제 앱을 μ‹œμž‘ν•˜κ³  web3에 자유둭게 μ ‘κ·Όν•  수 μžˆλ‹€.
  startApp()

})



🦠 슀마트 μ»¨νŠΈλž™νŠΈμ™€ 톡신할 수 있게 ν•œλ‹€.

web3κ°€ 슀마트 μ»¨νŠΈλž™νŠΈμ™€ ν†΅μ‹ ν•˜κΈ° μœ„ν•΄μ„œλŠ” μ»¨νŠΈλž™νŠΈμ˜ μ£Όμ†Œμ™€ ABIκ°€ ν•„μš”ν•˜λ‹€.

ABI (Application Binary Interface)
ν•΄λ‹Ή μ»¨νŠΈλž™νŠΈμ˜ λ©”μ†Œλ“œλ₯Ό ν‘œν˜„ν•˜λŠ” JSON ν˜•νƒœμ˜ μΈν„°νŽ˜μ΄μŠ€μ΄λ‹€. ν•΄λ‹Ή μ»¨νŠΈλž™νŠΈκ°€ 이해할 수 μžˆλŠ” λ°©λ²•μœΌλ‘œ web3κ°€ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•  수 μžˆκ²Œλ” μ•Œλ €μ€€λ‹€.

μ»¨νŠΈλž™νŠΈ μ£Όμ†Œμ™€ ABIλ₯Ό μ΄μš©ν•΄ μ΄μš©ν•˜κ³ μž ν•˜λŠ” μ»¨νŠΈλž™νŠΈλ₯Ό μΈμŠ€ν„΄μŠ€ν™” ν•œλ‹€.

var myContract = new web3js.eth.Contract(myABI, myContractAddress);



🦠 슀마트 μ»¨νŠΈλž™νŠΈμ™€ ν†΅μ‹ ν•œλ‹€.

web3κ°€ μ»¨νŠΈλž™νŠΈμ˜ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜κΈ° μœ„ν•΄μ„œ call, send 두 개의 λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€. λ©”μ†Œλ“œμ˜ 호좜 κ²°κ³ΌλŠ” Promiseμ΄λ―€λ‘œ(비동기) .then을 μ΄μš©ν•΄ κ²°κ³Όλ₯Ό 가지고 μ΄ν›„μ˜ μž‘μ—…μ„ μ²˜λ¦¬ν•΄μ€„ 수 μžˆλ‹€.


(1) πŸ“ž call

view, pure ν•¨μˆ˜λ₯Ό μœ„ν•΄ μ‚¬μš©ν•œλ‹€. 둜컬 λ…Έλ“œμ—μ„œλ§Œ μ‹€ν–‰ν•˜κ³  블둝체인에 νŠΈλžœμž­μ…˜μ„ λ§Œλ“€μ§€ μ•ŠλŠ”λ‹€. κ°€μŠ€λ„ μ†Œλͺ¨ν•˜μ§€ μ•ŠλŠ”λ‹€.

// μ’€λΉ„ id둜 μ’€λΉ„ 정보λ₯Ό κ°€μ Έμ˜€λŠ” call λ©”μ†Œλ“œ
function getZombieDetails(id) {
  return cryptoZombies.methods.zombies(id).call()
}

// ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜κ³  κ²°κ³Όλ₯Ό 가지고 무언가λ₯Ό μ²˜λ¦¬ν•  수 μžˆλ‹€.
getZombieDetails(15)
.then(function(result) {
  console.log("Zombie 15: " + JSON.stringify(result));
});

(2) πŸ’Œ send

view, pureκ°€ μ•„λ‹Œ λͺ¨λ“  ν•¨μˆ˜μ— λŒ€ν•΄ sendλ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€. sendλŠ” νŠΈλžœμž­μ…˜μ„ λ§Œλ“€κ³  블둝체인 μƒμ˜ 데이터λ₯Ό λ³€κ²½ν•œλ‹€. λ”°λΌμ„œ μ‚¬μš©μžλŠ” κ°€μŠ€λ₯Ό μ§€λΆˆν•΄μ•Ό ν•œλ‹€. ν”„λ‘œλ°”μ΄λ”λ‘œ λ©”νƒ€λ§ˆμŠ€ν¬λ₯Ό μ‚¬μš©ν•œλ‹€λ©΄, send()λ₯Ό ν˜ΈμΆœν–ˆμ„ λ•Œ μžλ™μœΌλ‘œ λ©”νƒ€λ§ˆμŠ€ν¬μ—μ„œ νŠΈλžœμž­μ…˜μ— μ„œλͺ…ν•˜λΌλŠ” 창을 λ„μ›Œμ€€λ‹€.
sendλŠ” νŠΈλžœμž­μ…˜μ„ 전솑해야 ν•˜κΈ° λ•Œλ¬Έμ— ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•œ μ‚¬λžŒ(dApp의 μ‚¬μš©μž)의 from μ£Όμ†Œκ°€ ν•„μš”ν•˜λ‹€. λ©”νƒ€λ§ˆμŠ€ν¬κ°€ 창을 λ„μ›Œ μ‚¬μš©μžκ°€ μ„œλͺ…을 ν•˜λ„λ‘ ν•œλ‹€. νŠΈλžœμž­μ…˜μ—λŠ” 였랜 μ‹œκ°„μ΄ μ†Œμš”λ  수 μžˆμœΌλ―€λ‘œ 이에 λŒ€μ‘ν•΄μ•Ό ν•œλ‹€. (νŠΈλžœμž­μ…˜μ΄ λ³΄λ‚΄μ‘Œλ‹€λŠ” μ•ˆλ‚΄λ„μš°κΈ°) λ˜ν•œ send λ©”μ†Œλ“œμ—λŠ” μ„ νƒμ μœΌλ‘œ gas와 gasPriceλ₯Ό 지정할 수 μžˆλ‹€. μ§€μ •ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄ λ©”νƒ€λ§ˆμŠ€ν¬λŠ” μ‚¬μš©μžκ°€ 이 값을 선택할 수 μžˆλ„λ‘ ν•œλ‹€.

function createRandomZombie(name) {
  // μ‹œκ°„μ΄ κ½€ 걸릴 수 μžˆμœΌλ‹ˆ, νŠΈλžœμž­μ…˜μ΄ λ³΄λ‚΄μ‘Œλ‹€λŠ” 것을
  // μœ μ €κ°€ μ•Œ 수 μžˆλ„λ‘ UIλ₯Ό μ—…λ°μ΄νŠΈν•΄μ•Ό 함
  $("#txStatus").text("Creating new zombie on the blockchain. This may take a while...");
  // 우리 μ»¨νŠΈλž™νŠΈμ— μ „μ†‘ν•˜κΈ°: μž…λ ₯된 μ΄λ¦„μœΌλ‘œ μƒˆλ‘œμš΄ μ’€λΉ„λ₯Ό μƒμ„±ν•˜λŠ” send λ©”μ†Œλ“œ
  return CryptoZombies.methods.createRandomZombie(name)
  .send({ from: userAccount }) // from μ£Όμ†Œκ°€ ν•„μš”ν•˜λ‹€.
  // μ’€λΉ„κ°€ μƒμ„±λ˜κ³  νŠΈλžœμž­μ…˜μ΄ μ„±κ³΅μ μœΌλ‘œ 블둝에 포함될 λ•Œ
  .on("receipt", function(receipt) {
    $("#txStatus").text("Successfully created " + name + "!");
    getZombiesByOwner(userAccount).then(displayZombies);
  })
  // κ°€μŠ€ λΆ€μ‘± λ“±μ˜ 이유둜 νŠΈλžœμž­μ…˜μ΄ μ‹€νŒ¨ν–ˆμ„ λ•Œ
  .on("error", function(error) {
    $("#txStatus").text(error);
  });
}

payable ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•  λ•Œμ—λŠ”, send λ©”μ†Œλ“œμ— from 외에 valueκΉŒμ§€ ν•¨κ»˜ μ„€μ •ν•΄μ€€λ‹€.

CryptoZombies.methods.levelUp(zombieId)
.send({ from: userAccount, value: web3js.utils.toWei("0.001") })



🦠 ν•„μš”μ‹œ λ©”νƒ€λ§ˆμŠ€ν¬μ—μ„œ μ‚¬μš©μž 계정을 κ°€μ Έμ˜¨λ‹€.

ν˜„μž¬ ν™œμ„±ν™”λœ 계정을 κ°€μ Έμ˜¬ 수 μžˆλ‹€.

var userAccount = web3.eth.accounts[0];

ν™œμ„±ν™”λœ 계정이 λ°”λ€Œκ²Œ 되면 ν”„λ‘ νŠΈ μΈν„°νŽ˜μ΄μŠ€μ— λ³΄μ—¬μ§€λŠ” 데이터도 바뀐 계정에 λ§žλŠ” λ°μ΄ν„°λ‘œ λ°”λ€Œμ–΄μ•Ό ν•˜λ―€λ‘œ, setInterval을 μ‚¬μš©ν•˜μ—¬ ν•΄λ‹Ή 계정이 계속 ν™œμ„±ν™”λ˜μ–΄ μžˆλŠ”μ§€ 확인해야 ν•œλ‹€.

var accountInterval = setInterval(function() {
  // 계정이 λ°”λ€Œμ—ˆλŠ”μ§€ 확인
  if (web3.eth.accounts[0] !== userAccount) {
    userAccount = web3.eth.accounts[0];
    // μƒˆ 계정에 λŒ€ν•œ UI둜 μ—…λ°μ΄νŠΈν•˜κΈ° μœ„ν•œ ν•¨μˆ˜ 호좜
    updateInterface();
  }
}, 100);



🦠 이벀트 ꡬ독/ν™œμš©ν•˜κΈ°

web3μ—μ„œ μ»¨νŠΈλž™νŠΈμ˜ 이벀트λ₯Ό κ΅¬λ…ν•˜μ—¬ ν•΄λ‹Ή μ΄λ²€νŠΈκ°€ λ°œμƒν•  λ•Œλ§ˆλ‹€ μ–΄λ– ν•œ λ‘œμ§μ„ μ‹€ν–‰μ‹œν‚€λ„λ‘ ν•  수 μžˆλ‹€. ν•˜μ§€λ§Œ dAppμ—μ„œ ν•΄λ‹Ή μ΄λ²€νŠΈκ°€ λ°œμƒν•˜λŠ” λͺ¨λ“  κ²½μš°μ— 둜직이 μž‘λ™ν•  것이닀. 이벀트λ₯Ό ν•„ν„°λ§ν•˜κ³  ν˜„μž¬ μ‚¬μš©μžμ™€ μ—°κ΄€λœ 이벀트만 μˆ˜μ‹ ν•˜κΈ° μœ„ν•΄ indexed ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);

μ»¨νŠΈλž™νŠΈ μ½”λ“œμ—μ„œ 이벀트λ₯Ό μ •μ˜ν•  λ•Œ _from, _toλ₯Ό indexed둜 μ •μ˜ν•˜μ˜€μœΌλ―€λ‘œ 이λ₯Ό ν™œμš©ν•˜μ—¬ ν•„ν„°λ§λœ μ΄λ²€νŠΈλ§Œμ„ ꡬ독할 수 μžˆλ‹€.

// `filter`λ₯Ό μ‚¬μš©ν•΄ `_to`κ°€ `userAccount`와 같을 λ•Œλ§Œ μ½”λ“œλ₯Ό μ‹€ν–‰
cryptoZombies.events.Transfer({ filter: { _to: userAccount } })
.on("data", function(event) {
  let data = event.returnValues;
  // ν˜„μž¬ μ‚¬μš©μžκ°€ 방금 μ’€λΉ„λ₯Ό λ°›μ•˜κ³ , ν•΄λ‹Ή μ’€λΉ„λ₯Ό 보여쀄 수 μžˆλ„λ‘ UIλ₯Ό μ—…λ°μ΄νŠΈν•˜λŠ” 둜직 μΆ”κ°€
  getZombiesByOwner(userAccount).then(displayZombies);
}).on("error", console.error);

λ˜ν•œ getPastEventsλ₯Ό μ‚¬μš©ν•˜μ—¬ μ§€λ‚œ μ΄λ²€νŠΈλ“€μ— λŒ€ν•΄ μ§ˆμ˜ν•  수 μžˆλ‹€. 이 λ•Œ fromBlockκ³Ό toBlock ν•„ν„°λ₯Ό μ΄μš©ν•΄ κ²€μƒ‰ν•˜κ³ μž ν•˜λŠ” 블둝 λ²”μœ„(이벀트 λ‘œκ·Έμ— λŒ€ν•œ μ‹œκ°„ λ²”μœ„)λ₯Ό 지정할 수 μžˆλ‹€. μ΄λŸ¬ν•œ 점을 ν™œμš©ν•˜μ—¬ 이벀트λ₯Ό μ €λ ΄ν•œ ν˜•νƒœμ˜ storage둜 μ‚¬μš©ν•  μˆ˜λ„ μžˆλ‹€. 데이터λ₯Ό 블둝체인에 κΈ°λ‘ν•˜λŠ” 것은 맀우 λΉ„μ‹Ό νŽΈμ΄μ§€λ§Œ 이벀트λ₯Ό μ΄μš©ν•˜λŠ” 것은 μƒλŒ€μ μœΌλ‘œ 훨씬 μ €λ ΄ν•˜λ‹€. 이벀트 λ°μ΄ν„°λŠ” 슀마트 μ»¨νŠΈλž™νŠΈ 자체 μ•ˆμ—μ„œλŠ” μ½κ±°λ‚˜ ν™œμš©ν•  수 μ—†μ§€λ§Œ, ν”„λ‘ νŠΈμ—”λ“œμ—μ„œ ν™œμš©ν•  νžˆμŠ€ν† λ¦¬ 데이터 κΈ°λ‘μš©μœΌλ‘œλŠ” μœ μš©ν•˜λ‹€.

cryptoZombies.getPastEvents("NewZombie", { fromBlock: 0, toBlock: "latest" })
.then(function(events) {
  // `events`λŠ” μš°λ¦¬κ°€ μœ„μ—μ„œ ν–ˆλ˜ κ²ƒμ²˜λŸΌ 반볡 μ ‘κ·Όν•  `event` κ°μ²΄λ“€μ˜ 배열이닀.
  // 이 μ½”λ“œλŠ” μƒμ„±λœ λͺ¨λ“  μ’€λΉ„μ˜ λͺ©λ‘μ„ μš°λ¦¬κ°€ 받을 수 있게 ν•  것이닀.
});




λΆ„λͺ…νžˆ 2μ£Ό 전에 크립토 μ’€λΉ„λ₯Ό λ‹€ ν–ˆλŠ”λ°, λ‹€μ‹œ ν•΄λ³΄λ‹ˆ 또 μƒˆλ‘œμ› λ‹€. 처음 ν•  λ•ŒλŠ” 섀렁섀렁 문제λ₯Ό ν’€κ³  λ„˜μ–΄κ°”λŠ”λ° μ΄λ²ˆμ—λŠ” κ΄€λ ¨ κ°œλ…μ„ ν™•μ‹€νžˆ λ¨Έλ¦Ώ 속에 λ„£λŠ”λ‹€ μƒκ°ν•˜κ³  μ •λ¦¬ν•˜λ©° μ§„ν–‰ν–ˆλ”λ‹ˆ solidityλ‚˜ web3에 λŒ€ν•œ 이해도가 많이 μ˜¬λΌκ°€λŠ” 것 κ°™λ‹€. λ©”νƒ€λ§ˆμŠ€ν¬ μ“΄ μ’€λΉ„ κ·€μ—½λ‹€!



profile
λ©‹μŸμ΄ 코린이

0개의 λŒ“κΈ€