티스토리 뷰

세 번째로는 이제 Advanced Solidity Concepts 에 대해 배운다..
얼마나 advanced된 것을 알려주려나... 나는,,, 두려울 따름..
우선 Chapter 1: Immutability of Contracts
설명을 읽어보니 컨트랙트를 이더리움에 deploy하면 immutable 하다고 한다. 나중에 엇! 뭐 잘못됐네! 고쳐야지~ 한다고 되는게 아니다. 그건 그냥 불변이다. 그 고장난 코드를 가진 채 계속 거기 있는 것이다...
그러다 생각해보니,, 그런 데이터를 다 어떻게 저장하지? 싶은것이다.. 중앙 저장소가 있다고 치면,, db 용량이 부족할거 같은데.. 이것 분산이고,, decentralized이니 다른가..? 하여 찾아보니, 온체인-오프체인 개념이 있다. 블록체인에서는 데이터 저장 비용과 용량 문제 해결을 위해 이 두 방식을 병행하는데, 온체인은 블록체인에 직접 기록되는 데이터를 가리키고, 중요한것, 보안이 필수적인 핵심 데이터만 여기에 둔다. 이건 모든 노드에 복제되므로 저장 비용이 비싸고 용량에도 한계까 있다. 장점은 탈중앙화돼있어서 투명하게 관리되고, 위변조가 불가능하다는것. 반면 오프체인의 경우 앞서 다른 온체인에 저장한 것들을 제외한 나머지 것들을 다루는데, 예를 들어 이런것들은 AWS같은 클라우드 서버나 IPFS 같은 분산 파일 시스템에 저장하고, 그 파일 주소나 해시값만 온체인에 올려두는 것이다.
신기방기..
Chapter 2: Ownable Contracts
이제 소유권에 대해 배운다. 어떤 함수 external로 해서 만들면 어디서든 호출이 된다. 이렇게 되면 보안이 취약해지는 것. 그래서 owner라는 개녕을 도입해서 특정 주소(예를 들면 소유자)에서만 주요 함수를 호출할 수 있도록 하는거다.
Ownable 이라고 OpenZeppelin 솔리디티 라이브러리에서 가져다가 많이 쓰나보다.
이번에도 ownable 임포트해서 Ownable 컨트랙트를 상속받아서 진행한다.
pragma solidity >=0.5.0 <0.6.0;
import "./ownable.sol";
contract ZombieFactory is Ownable {
event NewZombie(uint zombieId, string name, uint dna);
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
struct Zombie {
string name;
uint dna;
}
이런식으로.
Chapter 3: onlyWoner Function Modifier
이제 다음 챕터!
modifier, 이거는 조건 체크하고 그럴 때 쓰나보다. 우선 함수형식인데 함수로 쓰이지는 않고, 함수 끝자락에서 추가되는 형식으로 만들어진다.
pragma solidity >=0.5.0 <0.6.0;
/**
* @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 private _owner;
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor() internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @return the address of the owner.
*/
function owner() public view returns(address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner());
_;
}
/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns(bool) {
return msg.sender == _owner;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @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 {
_transferOwnership(newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0));
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
여기 보면 modifier onlyOwner가 만들어지고, 이거는 reuiqre로 조건 체크한다. _; 이 부분이 보일텐데, 조건이 맞으면, _; 이 자리에 이끌어가는 함수가 들어간다 보면 된다. renounceOwnershipi() 함수가 onlyOwner modifier를 쓰고 있다.
require 구분으로 조건을 체크하고, 조건이 맞으면, _; 이 부분을 만나서 원래 함수, 여기서는 renounceOwnership으로 제어권을 넘겨준다.
Chapter 4: Gas
이더리움 생태계에서 함수 연산시키고 하려면, 돈을 내야한다. 그 돈의 단위가 gas다. 가스비 얼마다~ 이런거 겨울에 보일러 켜고 그래서 나오는것 외에 이런것도 있다.
uint는 기본적으로 uint256으로 간주해서 기록되는데, uints에는 uint8 16 32도 있다. 이런 서브 타입 쓰는거의 이점이 보통 없는데, 256비츠를 쓰나 8비츠를 쓰나 가스비에는 차이가 없기 때문이다. 다만 한 가지 상황! struct를 쓸 때에는 상관이 있다. 변수들을 패킹하는데 작은 storage만 점유하도록 하면, 이건 가스비 절약에 도움이 되기 때문!
그리고 struct 구조체에서 같은 형식의 데이터끼리 묶어서 배열하는것도 가스비 절약에 도움이 된다. 구조체 안의 변수들이
`uint c; uint32 a; uint32 b;`
이렇게 나열된 것 보다
`uint32 a; uint c; uint32 b;`
이렇게 나열된게 가스비가 더 나온다.
Chapter 5: Time Units
now 변수는 유닉스 타임스탬프를 반환하는데, 이건 1970년 1월 1일 이후 경과된 seconds를 의미한다.
그리고 시간 단위 키워드도 쓸 수 있다. 1일 기준 잡고싶다면? 1 days 쓰면 된다.
seconds, minutes, hours, days, weeks, years 모두 사용할 수 있는 키워드!
Chapter 6: Zombie Cooldowns
여기서는 struct 구조체를 함수 인자로 넘기는것도 배운다.
function _doStuff(Zombie storage _zombie) internal {
// do stuff with _zombie
}
이렇게 하면 구조체를 storage 포인터로 전달하는건데, 이거는 구조체의 주소를 참조할 수 있게 직접 넘겨주는 것과 같다.
이미 찾아서 메모리에 올려놓은 데이터를 그대로 사용하니까, 추가적인 검색 연산(예를 들어 zombie id로 검색하는거)이 필요 없고, 이에 따라 가스비를 절약하고 컨트랙트 효율성을 높일 수 있다.
Chapter 7: Public Functions & Security
여기서는 이제 public으로 정의된 함수를 internal로 바꾼다. 보안 취약점도 있고, 프로그램 로직 흐름상 internal로 만들어도 충분하기 때문에.
그리고 require 구문을 추가해서 쿨타임이 지났는지 확인하고, 함수 마지막엔 다시 쿨타임이 시작되도록 함수를 추가한다.
Chapter 8: More on Function Modifiers
챕터별로 뭐 쓰려니 안되겠네. 그냥 학습하면서 적어둬야겠다 싶은거 따로 적어야겠다.
여기서는 Modifiers에 대해서 조금 더 배우려나보다. 챕터 제목 보니까 알겠다.
modifier도 함수처럼 인자 받을 수 있다구. modifier도 일종의 함수니까 당연한 것 같음.
pragma solidity >=0.5.0 <0.6.0;
import "./zombiefeeding.sol";
contract ZombieHelper is ZombieFeeding {
modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}
}
Chapter 10: Saving Gas with "View" Functions
뷰 함수는 블록체인의 상태를 변경하지 않고 단순히 읽기만 한다. 그래서 컨트랙트 외부에서 호출하는 external로 쓰일 대는 가스비가 들지 않는다. 노드들이 상태 검증을 할 필요가 없기 때문이다.
하지만, 컨트랙트 내부에서 다른 함수가 view를 호출하면 상황이 달라진다. 이렇게 view를 호출하면, 블록체인에 트랜잭션을 일으키는거고, 모든 노드가 이 트랜잭션을 검증할 필요가 생긴다. 그래서 view 자체가 상태를 바꾸지 않더라도, 트랜잭션의 일부로 실행이 되기 때문에 가스비가 나간다.
function getZombiesByOwner(address _owner) external view returns(uint[] memory) {
}
이렇게 하나 만들어봄... 반환 타입 저장 위치 (memory, storage)는 그 반환 타입 뒤에 붙인다는걸 새로 알게됨...
Chapter 11: Storage is Expensive
제목부터 스토리지 비싸다고 소리치는 것 같다. 스토리지에 쓰는게 정말 비싼가보구나.
그렇게 써있다. 스토리지 쓰는게 비싼데, 특히 비싼게 쓰는거라구. write
왜냐하면 매 번 내가 데이터 일부를 변경하거나, 쓰거나 하면, 블록체인에 이게 영구적으로 쓰이는거기 때문이다. 영! 원! 히!
보통 프로그래밍에서 대용량의 데이터에 대해 순회하는거(looping)은 비싼 연산으로 간주되지만, 솔리디티에서만큼은 스토리지를 쓰는 것보다 external view로 메모리에 올려서 순회하는게 더 저렴하다.
function getZombiesByOwner(address _owner) external view returns(uint[] memory) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
return result;
}
이렇게 해 두고, 다음 챕터로 넘어간다.
Chapter 12: For Loops
여기 되게 좋은 설명이 나온다. 자료구조를 왜 공부해야 하나를 여기서 알 수 있는건가 싶지만 나는 잘 모르니 머..
애초에 maping (address => uint[]) publilc ownerToZombies 이런 식으로 소유자별로 좀비 ID 배열을 따로 저장하는 방식을 생가갷 볼 수 있다. 이렇게 저장해두면, getZombieByOwner 함수를 만들 때 매우 간단할거다. 그냥 소유자만 부르면 좌르륵 좀비 군단이 출력될테니까 말이다.
하지만 이건 가스비 관점에서 보았을 때 되게 비효율적이고, 고비용 연산이다. 이 좀비 군단을 다른 사용자에게 양도하는 transfer 함수를 만든다고 가정해보면, 원래 사용자 배열에서 한 좀비를 지우고, 다른 사용자, 새로운 사용자의 좀비 배열에 하나를 추가해야 한다. 이렇게되면 원래 소유자의 좀비 군단에서, 모든 좀비들은 한 칸씩 옮겨 앉아야한다. 이건 storage에 대한 쓰기 작업이 여러번 반복되므로 가스비가 많이 든다. 게다가 각각의 소유자가 가지는 좀비 수에 따라 가스비도 달라지므로, 정확한 가스비 예측도 어렵다.
이에 반해서, Ziombie[] public zombies 와 같이, 모든 좀비 데이터를 하나의 큰 배열에 저장한다. 이 배열에는 모든 좀비 정보가 순서대로 들어있고, 이중에는 owner 정보도 포함돼 있다.
이 경우 getZombiesByOwner 함수를 만든다면, for 루프를 활용해서 zombies 전체 배열을 순회해야 할 것이다. 각 좀비가 가진 owner 정보와 함수가 인자로 가지는 _owner 값을 비교해서, 일치하면 새로운 배열에 담는거다.
view 함수는 external 호출시 가스비가 무료다. 그러므로 배열 순회시 가스비가 안든다. 또한 transfer 함수를 가정해 보았을 때, 이 상황에서는 개별 좀비들이 자리를 당겨 앉을 필요가 없고, owner 정보만 업데이트 하면 된다. 훨씬 저렴한 쓰기 연산 한 번으로 끝나는것이다.
그래서 여기서는 자바스크립트와 비슷한 모양새의 for loop를 만든다.
function getZombiesByOwner(address _owner) external view returns(uint[] memory) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
uint counter = 0;
for (uint i = 0; i<zombies.length; i++) {
if (zombieToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}
return result;
굳~
- Total
- Today
- Yesterday
- 솔리디티
- 테스트넷
- setState
- contains
- til
- 터틀포인트
- prettier
- Flutter
- 길리
- 세상만사새옹지마
- 세폴리아
- vscode
- web2
- 플러터
- 오블완
- 김영한
- web3
- 선라이즈 패들보트
- 티스토리챌린지
- 찾아봄
- 스파르타코딩클럽
- gili
- 길리여행
- 크립토좀비
- 이더리움
- DART
- 패들보트
- 공부좀열심히해라
- web2web3비교
- zksync
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |