솔리디티(Solidity) 정리

박정호·2022년 5월 9일
0

솔리디티를 공부하면서 러프하게 정리했던 글이 있어 업로드를 해둔다.

  1. (스마트) 컨트랙트는 계약인데 일단 클래스 느낌이라고 익혀두자
  2. 프라그마는 필수는 아니라고 하기는 하던 데 일단 필수라고 생각하고 있자
  3. 변수, 구조체, 배열, 매핑, 어드레스, 자료형
  4. 구조체는 변수의 모음 집합
  5. 구조체, 즉 어떠한 집합, 쌍 느낌으로 선언할 때 배열 매핑을 사용하자. 배열은 배열이고, 매핑은 객체이다.
    뭐 당연한 말이지 구조체라고 해도 결국은 객체고, 객체에다 타입 정확하게 적어준 클래스 느낌이 구조체니까
  6. public 배열은 공개 데이터로 쓰자. 배열을 쓸 수 없고 읽을 수만 있다
  7. 함수 : 함수 인자명을 언더스코어(_)로 시작해서 전역 변수와 구별하는 것이 관례이네
  8. 솔리디티에서 함수는 기본적으로 public이기 때문에 무조건 시작을 private로 잡고 공개할 것만 public으로 쓰게끔 습관을 가지자
  9. 함수 인자명과 마찬가지로 private 함수명도 언더바(_)로 시작하는 것이 관례라네.
  10. pure 함수는 걍 사칙연산에 수 넣는 느낌이라고 생각하면댐. 어디서 멀 불러오면 안댐
  11. 형 변환이 있다는 것만 알고있자
  12. 이벤트는 그냥 인풋, 버튼 느낌을 떠올리자. 그게 제일 직관적이다.
  13. 모든 곳에서 일단 선언한다 느낌 있으면 타입을 선언해줘야 한다.
  14. msg.sender : 함수를 호출한 사람
  15. 솔리디티에서 함수 실행은 항상 외부 호출자가 시작하네. 컨트랙트는 누군가가 컨트랙트의 함수를 호출할 때까지 블록체인 상에서 아무 것도 안 하고 있을 것이네. 그러니 항상 msg.sender가 있어야 하네.
  16. 각 플레이어가 이 함수를 한 번만 호출할 수 있도록 만들어 보세. 난 이거 배열로 처리를 했는데 다른 방법을 제시하네.
  17. require를 활용하면 특정 조건이 참이 아닐 때 함수가 에러 메시지를 발생하고 실행을 멈추게 되지. 걍 if문인데?
  18. 상속은 일반 클래스와 똑같다. 그럼 상속에서 프라이빗 함수는? 안된다. 그래서 방법이 있다.
  19. import시 export는 꼭 써야하는 건가? =>
  20. internal은 함수가 정의된 컨트랙트를 상속하는 컨트랙트에서도 접근이 가능하다 점을 제외하면 private과 동일하지
  21. external은 함수가 컨트랙트 바깥에서만 호출될 수 있고 컨트랙트 내의 다른 함수에 의해 호출될 수 없다는 점을 제외하면 public과 동일하지.
  22. internal로 하면 상속한 곳에서도 쓸 수 있다. external은 바깥에서만 쓸 수 있다 같은데??
  23. 최종 정리해서 코드 내에서 뭘 한다 = private, 나중에 누구에게도 쓸 수 있는 함수를 만들거다 = public
  24. private를 상속받은 곳에서도 쓰게 해줄거다 = internal, public인데 원래 곳에서는 못쓴다 = external,
  25. 대신 internal, external을 통해 함수로 어떠한 것을 수정하고 그런 과정을 거칠 수 있나보다
  26. 솔리디티 데이터 저장 장소 스토리지(로컬, 블록체인), 메모리(램)
  27. 전역변수, 지역변수 말하고자 하는 것 같은데 일단 이건 넘어가고
  28. 포인트는 참조할 건지, 복제를 할 건지 이거 말하는 거네
  29. 구조체에서 storage 는 걍 가져오는 거고, memory는 사본 하나 만들어서 그걸로 처리하는 느낌이라고 생각하자.
  30. 포인터 개념이 나오네
  31. 디앱 : 자네가 이더리움에 컨트랙트를 배포하고 나면, 컨트랙트는 변하지 않는다네(Immutable). 다시 말하자면 컨트랙트를 수정하거나 업데이트할 수 없다는 것이지.
  32. 보안 뚫린거 잘못 배포하면 좆된다는 소리
  33. 남이 못 만지도록 ownable 라이브러리 이용 많이 한다. 쉽게 정리하면 소유 할걸 ownable로 감싸고, 이제 소유했니 이런 조건을 넣을 때 onlyOwner를 쓰면 되네
  34. 상속을 하고하고 하다보면 그동안 만났던 문제 즉, 상태관리 문제가 나오는 거지. 함수형을 쓰는 것 자체도 그런 관계는 내가 씨발 건드리기 싫다해서 나온 거니까
  35. 솔리디티에서는 uint의 크기에 상관없이 256비트의 저장 공간을 미리 잡아놓기 때문이지. 예를 들자면, uint(uint256) 대신에 uint8을 쓰는 것은 가스 소모를 줄이는 데에 아무 영향이 없네.
    하지만 여기에 예외가 하나 있지. 바로 struct의 안에서라네. 이런 이유로, 구조체 안에서는 자네는 가능한 한 작은 크기의 정수 타입을 쓰는 것이 좋네.
  36. 이 함수들이 onlyOwner 같은 제어자를 갖지 않는 이상, 어떤 사용자든 이 함수들을 호출하고 자신들이 원하는 모든 데이터를 함수에 전달할 수 있네.
  37. 그러니 이런 남용을 막을 가장 쉬운 방법은 이 함수를 internal로 만드는 것이지.
  38. require던 modifier던 왜 if문 대신 나온거지? 이유가 있지 않겠어?
  39. 함수 나머지 부분 실행하기 위해 _; 쓰기
  40. view 함수는 가스를 소모하지 않음
  41. 참고: 만약 view 함수가 동일 컨트랙트 내에 있는, view 함수가 아닌 다른 함수에서 내부적으로 호출될 경우, 여전히 가스를 소모할 것이네. 이것은 다른 함수가 이더리움에 트랜잭션을 생성하고, 이는 모든 개별 노드에서 검증되어야 하기 때문이네. 그러니 view 함수는 외부에서 호출됐을 때에만 무료라네.
  42. 비용을 최소화하기 위해서, 진짜 필요한 경우가 아니면 storage에 데이터를 쓰지 않는 것이 좋네.
  43. 대부분의 프로그래밍 언어에서는, 큰 데이터 집합의 개별 데이터에 모두 접근하는 것은 비용이 비싸네. 하지만 솔리디티에서는 그 접근이 external view 함수라면 storage를 사용하는 것보다 더 저렴한 방법이네. view 함수는 사용자들의 가스를 소모하지 않기 때문이지(가스는 사용자들이 진짜 돈을 쓰는 것이네!).
  44. Storage에 아무것도 쓰지 않고도 함수 안에 새로운 배열을 만들려면 배열에 memory 키워드를 쓰면 되네. 이 배열은 함수가 끝날 때까지만 존재할 것이고, 이는 storage의 배열을 직접 업데이트하는 것보다 가스 소모 측면에서 훨씬 저렴하네 - 외부에서 호출되는 view 함수라면 무료이지.
  45. 참고: 메모리 배열은 반드시 길이 인수와 함께 생성되어야 하네(이 예시에서는, 3). 메모리 배열은 현재로서는 storage 배열처럼 array.push()로 크기가 조절되지는 않네. 이후 버전의 솔리디티에서는 변경될 수도 있겠지만 말이야.
  46. 3번째 단계는 극단적으로 가스 소모가 많을 것이네. 왜냐하면 위치를 바꾼 모든 좀비에 대해 쓰기 연산을 해야 하기 때문이지. 소유자가 20마리의 좀비를 가지고 있고 첫 번째 좀비를 거래한다면, 배열의 순서를 유지하기 위해 우린 19번의 쓰기를 해야 할 것이네. 솔리디티에서 storage에 쓰는 것은 가장 비용이 높은 연산 중 하나이기 때문에, 이 전달 함수에 대한 모든 호출은 가스 측면에서 굉장히 비싸게 될 것이네. 더 안 좋은 점은, 이 함수가 실행될 때마다 다른 양의 가스를 소모할 것이라는 점이네. 사용자가 자신의 군대에 얼마나 많은 좀비를 가지고 있는지, 또 거래되는 좀비의 인덱스에 따라 달라지겠지. 즉 사용자들은 거래에 가스를 얼마나 쓰게 될지 알 수 없게 되네. 참고: 물론, 빈 자리를 채우기 위해 마지막 좀비를 움직인 다음, 배열의 길이를 하나 줄여도 되겠지. 하지만 그렇게 하면 교환이 일어날 때마다 좀비 군대의 순서가 바뀌게 될 것이네. view 함수는 외부에서 호출될 때 가스를 사용하지 않기 때문에, 우린 getZombiesByOwner 함수에서 for 반복문을 사용해서 좀비 배열의 모든 요소에 접근한 후 특정 사용자의 좀비들로 구성된 배열을 만들 수 있을 것이네. 그러고 나면 transfer 함수는 훨씬 비용을 적게 쓰게 되겠지. 왜냐하면 storage에서 어떤 배열도 재정렬할 필요가 없으니까 말이야. 일반적인 직관과는 반대로 이런 접근법이 전체적으로 비용 소모가 더 적네.
  47. 쉽게 말해서 메모리에다 쓰고 버리는 형식을 한번 고려해보자 이소리야.
  48. 지금까지 우린 꽤 많은 함수 제어자(function modifier)를 다뤘네. 모든 것을 다 기억하는 것은 힘들 것이니, 한번 빠르게 복습해보세.
    우린 함수가 언제, 어디서 호출될 수 있는지 제어하는 접근 제어자(visibility modifier)를 알게 되었네: private은 컨트랙트 내부의 다른 함수들에서만 호출될 수 있음을 의미하지. internal은 private과 비슷하지만, 해당 컨트랙트를 상속하는 컨트랙트에서도 호출될 수 있지. external은 오직 컨트랙트 외부에서만 호출될 수 있네. 마지막으로 public은 내외부 모두에서, 어디서든 호출될 수 있네.
    또한 상태 제어자(state modifier)에 대해서도 배웠네. 이 제어자는 블록체인과 상호작용 하는 방법에 대해 알려주지: view는 해당 함수를 실행해도 어떤 데이터도 저장/변경되지 않음을 알려주지. pure는 해당 함수가 어떤 데이터도 블록체인에 저장하지 않을 뿐만 아니라, 블록체인으로부터 어떤 데이터도 읽지 않음을 알려주지. 이들 모두는 컨트랙트 외부에서 불렸을 때 가스를 전혀 소모하지 않네(하지만 다른 함수에 의해 내부적으로 호출됐을 경우에는 가스를 소모하지).
    그리고 사용자 정의 제어자에 대해서도 배웠네. 레슨 3에서 배웠던 것이지. 예를 들자면 onlyOwner와 aboveLevel 같은 것이지. 이런 제어자를 사용해서 우린 함수에 이 제어자들이 어떻게 영향을 줄지를 결정하는 우리만의 논리를 구성할 수 있네. 이런 제어자들은 함수 하나에 다음처럼 함께 사용할 수 있네:
    function test() external view onlyOwner anotherModifier { / ... / }
  49. payable 제어자 : 이는 이더를 받을 수 있는 특별한 함수 유형이지.
  50. msg.value는 컨트랙트로 이더가 얼마나 보내졌는지 확인하는 방법이고, ether는 기본적으로 포함된 단위이네.
  51. 자네는 transfer 함수를 사용해서 이더를 특정 주소로 전달할 수 있네. 그리고 this.balance는 컨트랙트에 저장돼있는 전체 잔액을 반환하지.
  52. 자네는 transfer 함수를 써서 특정한 이더리움 주소에 돈을 보낼 수 있네. 예를 들어, 만약 누군가 한 아이템에 대해 초과 지불을 했다면, 이더를 msg.sender로 되돌려주는 함수를 만들 수도 있네:
  53. 솔리디티에서 난수를 만들기에 가장 좋은 방법은 keccak256 해시 함수를 쓰는 것이네.
  54. 이더리움에서는 자네가 컨트랙트의 함수를 실행하면 트랜잭션(transaction)으로서 네트워크의 노드 하나 혹은 여러 노드에 실행을 알리게 되네. 그 후 네트워크의 노드들은 여러 개의 트랜잭션을 모으고, "작업 증명"으로 알려진 계산이 매우 복잡한 수학적 문제를 먼저 풀기 위한 시도를 하게 되네. 그리고서 해당 트랜잭션 그룹을 그들의 작업 증명(PoW)과 함께 블록으로 네트워크에 배포하게 되지.
  55. 한 노드가 어떤 PoW를 풀면, 다른 노드들은 그 PoW를 풀려는 시도를 멈추고 해당 노드가 보낸 트랜잭션 목록이 유효한 것인지 검증하네. 유효하다면 해당 블록을 받아들이고 다음 블록을 풀기 시작하지.
  56. 4-4 다시 볼 것. 중요한 내용
  57. 공통 로직 구조 개선하기(Refactoring)
  58. 누가 우리의 attack 함수를 실행하든지 - 우리는 사용자가 공격에 사용하는 좀비를 실제로 소유하고 있다는 것을 확실히 하고 싶네. 만약 자네가 다른 사람의 좀비를 사용해서 공격할 수 있다면 보안에 문제가 되는 부분일 것이야!
  59. 함수를 호출하는 사람이 그가 사용한 _zombieId의 소유자인지 확인할 방법을 생각해낼 수 있겠는가?
  60. 우리의 attack 함수에도 똑같은 내용을 적용할 필요가 있네. 동일한 내용을 여러 번 사용하고 있으니, 코드를 정리하고 반복을 피할 수 있도록 이 내용을 이것만의 modifier로 옮기도록 하세.
  61. 리팩토링은 상속에서 힘을 발휘하네 결국은.
  62. 우린 DApp에서 다양한 방식으로 이 데이터를 저장할 수 있네 - 개별적인 매핑으로, 순위표 구조체로, 혹은 Zombie 구조체 자체에 넣을 수도 있네.
    우리가 이 데이터로 어떻게 상호작용 할 것인지에 따라 각각의 방식 모두 장단점이 있네. 이 튜토리얼에서는, 간결함을 유지할 수 있도록 Zombie 구조체에 상태를 저장하도록 하고, 이들을 winCount와 lossCount로 이름짓도록 하겠네.
  63. 이더리움에서 토큰은 기본적으로 그저 몇몇 공통 규약을 따르는 스마트 컨트랙트이네 — 즉 다른 모든 토큰 컨트랙트가 사용하는 표준 함수 집합을 구현하는 것이지. 예를 들면 transfer(address _to, uint256 _value)나 balanceOf(address _owner) 같은 함수들이 있네.
  64. ERC20 토큰은 화폐처럼 사용되는 토큰으로는 정말 적절하네. 하지만 우리의 좀비 게임에서 좀비를 표현할 때에는 그다지 쓸모 있지가 않지.
  65. ERC721 토큰은 교체가 불가하네. 각각의 토큰이 유일하고 분할이 불가하기 때문이지. 자네는 이 토큰을 하나의 전체 단위로만 거래할 수 있고, 각각의 토큰은 유일한 ID를 가지고 있네. 그러니 이게 우리의 좀비를 거래할 수 있게 하기에는 아주 적절하지.
  66. 운 좋게도 솔리디티에서는, 자네의 컨트랙트는 다음과 같이 다수의 컨트랙트를 상속할 수 있네:
  67. uint256은 uint와 동일하다는 것을 기억하게.
  68. 다시 말하지만, 이들은 구현하기가 매우 수월하네. 우리가 이 정보를 저장하는 mapping을 우리 DApp에 이미 가지고 있기 떄문이지. 이 함수들은 단 한 줄로 구현할 수 있네. return 문장 하나만 가지고 말이야.
  69. 상속을 받았다는 소리는 부모의 뭘 쓰겠다는 뜻이지? 함수명은 꼭 보고 같은지 확인해주자
  70. ERC721 스펙에서는 토큰을 전송할 때 2개의 다른 방식이 있음을 기억하게:
  71. 첫 번째 방법은 토큰의 소유자가 전송 상대의 address, 전송하고자 하는 _tokenId와 함께 transfer 함수를 호출하는 것이네.
    두 번째 방법은 토큰의 소유자가 먼저 위에서 본 정보들을 가지고 approve를 호출하는 것이네. 그리고서 컨트랙트에 누가 해당 토큰을 가질 수 있도록 허가를 받았는지 저장하지. 보통 mapping (uint256 => address)를 써서 말이지. 이후 누군가 takeOwnership을 호출하면, 해당 컨트랙트는 이 msg.sender가 소유자로부터 토큰을 받을 수 있게 허가를 받았는지 확인하네. 그리고 허가를 받았다면 해당 토큰을 그에게 전송하지.
  72. 5-9부터 다시 보자

사용법 정리

methods.변수().call()
methods.함수(인자).send().on()
events.이벤트().on(data, error, extra)

변수 모음 : 변수도 선언이 끝, 변수는 보통 클라이언트에서 콜해서 가져오겠지
토탈서플라이
토큰유알아이
오너오프
오퍼카운트
오퍼
유저펀즈

이벤트 모음 : 이벤트는 선언이 끝, 그리고 클라이언트에서 이벤트가 나왔을 때 어떤 처리를 써줄 수 있겠지
트랜스퍼
오퍼필드
오퍼
오퍼캔슬드
클레임펀즈

함수 모음 : 함수는 거의 마지막에 이벤트를 emit 하는데?
세이프민트
어프로브
메이크오퍼
필오퍼
캔슬오퍼
클레임펀즈

offer makeoffer : 구매 시작할테니 살사람 사세요를 공표하는데
상품 주인에게 뭘 한다. 트랜스퍼프롬으로(나, 어떤 주소, 어떤 아이디)
배열에 뭘 담고

buy filloffer : 누가 사면 페이어블을 감싸주고, 사려는 사람과, 어떤 어드레스를 연결시켜주는 거 같은데
트랜스퍼프롬(주소, 나, 어떤 아이디)

cancle cancleoffer : 상품 주인이 ㅇㅋ 너한테 넘김 으로 하는 거니까

claimfunds 이건 아예 소유권 넘기는거

profile
개발하기

0개의 댓글