Solidity - 기본

choko's avatar
Jun 29, 2024
Solidity - 기본

Solidity - 기본

 
  • private : 관례상 함수 이름을 _로 시작한다
  • view: 함수가 상태를 변경하지 않는 경우, view를 붙인다.
  • pure: 함수가 앱에서 어떤 데이터도 접근하지 않는다.
  • keccak256: sha3의 한 버전인 내장 해시 함수
keccak256("aaaab"); //6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5 keccak256("aaaac"); //b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
  • 이벤트: 컨트랙트가 블록체인 상에서 앱의 액션이 발생했을때 행동을 취함
 
  • mapping (map)
    • 솔리디티에서 구조화된 데이터를 저장하는 또다른 방법
    • 기본적으로 key-value 구조로, 데이터를 저장하고 검색하는데 이용
 
*// 금융 앱용으로, 유저의 계좌 잔액을 보유하는 uint를 저장한다:* mapping (address => uint) public accountBalance; *// 혹은 userID로 유저 이름을 저장/검색하는 데 매핑을 쓸 수도 있다* mapping (uint => string) userIdToName;
 
 
  • msg.sender
    • 솔리디티에는 모든 함수에서 이용 가능한 특정 전역 변수들이 있다.
    • msg.sender는 현재 스마트 컨트랙트를 호출한 사람의 주소를 가리킨다.
    •  
  • mapping과 msg.sender 이용 예시
mapping (address => uint) favoriteNumber; function setMyNumber(uint _myNumber) public { // `msg.sender`에 대해 `_myNumber`가 저장되도록 `favoriteNumber` 매핑을 업데이트한다 ` favoriteNumber[msg.sender] = _myNumber; // ^ 데이터를 저장하는 구문은 배열로 데이터를 저장할 떄와 동일하다 } function whatIsMyNumber() public view returns (uint) { // sender의 주소에 저장된 값을 불러온다 // sender가 `setMyNumber`을 아직 호출하지 않았다면 반환값은 `0`이 될 것이다 return favoriteNumber[msg.sender]; }
 
 
  • require
    • 특정 조건이 참이 아닐 때 함수가 에러 메세지를 발생하고 실행을 멈추게 된다.
    • function sayHiToVitalik(string _name) public returns (string) { // _name이 "Vitalik"인지 비교한다. 참이 아닐 경우 에러 메시지를 발생하고 함수를 벗어난다 // (참고: 솔리디티는 고유의 스트링 비교 기능을 가지고 있지 않기 때문에 // 스트링의 keccak256 해시값을 비교하여 스트링 값이 같은지 판단한다) require(keccak256(_name) == keccak256("Vitalik")); // 참이면 함수 실행을 진행한다: return "Hi!"; }
       
  • 상속
    • contract Doge { function catchphrase() public returns (string) { return "So Wow CryptoDoge"; } } contract BabyDoge is Doge { function anotherCatchphrase() public returns (string) { return "Such Moon BabyDoge"; } }
    • BabyDoge는 Doge를 상속한다.
 
 
  • Storage vs Memory
    • 솔리디티에는 변수를 저장할 수 있는 공간이 storage와 memory 두 가지가 있다.
    • Storage
      • 블록체인 상에 영구적으로 저장되는 변수
    • Memory
      • 임시적으로 저장되는 변수로, 컨트랙트 함수에 대한 외부 호출들이 일어나는 사이 지워짐
    • 대부분의 경우 솔리디티가 알아서 처리해 주지만, 함수 내에 구조체와 배열을 처리할 때는 사용할 때가 있다.
contract SandwichFactory { struct Sandwich { string name; string status; } Sandwich[] sandwiches; function eatSandwich(uint _index) public { // Sandwich mySandwich = sandwiches[_index]; // ^ 꽤 간단해 보이나, 솔리디티는 여기서 // `storage`나 `memory`를 명시적으로 선언해야 한다는 경고 메시지를 발생한다. // 그러므로 `storage` 키워드를 활용하여 다음과 같이 선언해야 한다: Sandwich storage mySandwich = sandwiches[_index]; // ...이 경우, `mySandwich`는 저장된 `sandwiches[_index]`를 가리키는 포인터이다. // 그리고 mySandwich.status = "Eaten!"; // ...이 코드는 블록체인 상에서 `sandwiches[_index]`을 영구적으로 변경한다. // 단순히 복사를 하고자 한다면 `memory`를 이용하면 된다: Sandwich memory anotherSandwich = sandwiches[_index + 1]; // ...이 경우, `anotherSandwich`는 단순히 메모리에 데이터를 복사하는 것이 된다. // 그리고 anotherSandwich.status = "Eaten!"; // ...이 코드는 임시 변수인 `anotherSandwich`를 변경하는 것으로 // `sandwiches[_index + 1]`에는 아무런 영향을 끼치지 않는다. 그러나 다음과 같이 코드를 작성할 수 있다: sandwiches[_index + 1] = anotherSandwich; // ...이는 임시 변경한 내용을 블록체인 저장소에 저장하고자 하는 경우이다. } }
 
 
  • internal, external
    • internal, external을 선언하는 구문은 private, public을 선언하는 구문과 동일하다.
    • internal
      • 함수가 정의된 컨트랙트를 상속하는 컨트랙트에서도 접근이 가능하다.
    • external
      • 함수가 컨트랙트 바깥에서만 호출될 수 있고 컨트랙트 내의 다른 함수에 의해 호출될 수는 없다
      •  
 
  • 솔리디티는 다수의 반환값을 처리할 수 있다.
function multipleReturns() internal returns(uint a, uint b, uint c) { return (1, 2, 3); } function processMultipleReturns() external { uint a; uint b; uint c; // 다음과 같이 다수 값을 할당한다: (a, b, c) = multipleReturns(); } // 혹은 단 하나의 값에만 관심이 있을 경우: function getLastReturnValue() external { uint c; // 다른 필드는 빈칸으로 놓기만 하면 된다: (,,c) = multipleReturns(); }
 
 
  • 컨트랙트의 불변성
    • 불변성
      • 이더리움 컨트랙트에 배포하고 나면, 컨트랙트는 변하지 않는다(컨트랙트를 수정하거나 업데이트 할 수 없다)
      • 컨트랙트에 배포한 최초의 코드는 항상 블록체인에 영구적으로 존재한다
      •  
 
  • OpenZeppelin
    • OpenZeppelin은, 안전하고 커뮤니티에서 검증받은 스마트 컨트랙트 라이브러리이다.
    • OpenZeppelin의 Ownable 컨트랙트
    • /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() public { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner); _; } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0)); OwnershipTransferred(owner, newOwner); owner = newOwner; } }
    • modifier
      • 함수 제어자로, 제어자는 다른 함수들에 대한 접근을 제어하기 위해 사용되는 일종의 유사 함수이다.
      • 보통 함수 실행 전의 요구사항 충족 여부를 확인하는데 사용된다.
      • 함수 제어자는, 직접 호출할 수 없다.
      • onlyOwner() 의 경우 오직 컨트랙트 소유자만 해당 함수를 실행할 수 있도록 하였다.
      • _; 키워드
        • Ownable 를 상속한 MyContract에서, onlyOwner()을 extend 하여 사용한다고 할때
        • contract MyContract is Ownable { event LaughManiacally(string laughter); // 아래 `onlyOwner`의 사용 방법을 잘 보게: function likeABoss() external onlyOwner { LaughManiacally("Muahahahaha"); } }
        • 이 경우, likeABoss → onlyOwner_; 부분을 lifeABoss 함수로 되돌아가 해당 코드를 실행하게 된다.
       
       
  • 가스
    • DApp의 함수를 실행할 때마다 가스라고 불리는 화폐를 지불하여야 한다.
    • 각각의 연산은 소모되는 가스 비용이 있고, 그 연산을 수행하는데에 소모되는 컴퓨팅 자원의 양이 이 비용을 결정한다.
    • 따라서, 이더리움에서 코드 최적화는 다른 프로그래밍 언어들에 비해 훨씬 더 중요하다.
      • 구조체 안에서는 가능한 작은 크기의 정수 타입을 쓰는것이 좋다.
      • 동일한 데이터 타입은 하나로 묶어놓는 것이 저장 공간을 최소화 시킨다.
      • uint c; uint32 a; uint32 b; 가 uint32 a; uint c; uint32 b; 보다 효율적
      •  
Share article

Tom의 TIL 정리방