이더리움 애플리케이션의 기본적인 구성 요소
모든 솔리디티 소스 코드는 "version pragma"로 시작해야 하는데, 이는 해당 코드가 이용해야 하는 솔리디티 버전을 선언하는 것
이후에 새로운 컴파일러 버전이 나와도 기존 코드가 깨지지 않도록 예방
컨트랙트 저장소에 영구적으로 저장되는 변수
ex) uint8, uint16, uint32, uint(256) 등
여러 자료형을 담을 수 있는 구조체
struct Person {
uint age;
string name;
}
다수의 같은 자료형 요소들을 담을 수 있는 구조체
// 2개의 원소를 담을 수 있는 고정 길이의 배열:
uint[2] fixedArray;
// 또다른 고정 배열으로 5개의 스트링을 담을 수 있다:
string[5] stringArray;
// 동적 배열은 고정된 크기가 없으며 계속 크기가 커질 수 있다:
uint[] dynamicArray;
Person[] people;
public
다른 컨트랙트들이 이 배열을 읽을 수 있음
Person[] public people;
private
컨트랙트 내의 다른 함수들만이 이 함수를 호출하여 사용할 수 있음
솔리디티에서 함수 선언은 반환값 종류를 포함
function sayHello() public returns (string) {
return greeting;
}
데이터를 보기만 하고 변경하지 않는 함수
function sayHello() public view returns (string) {
}
앱에서 어떤 데이터도 접근하지 않는 함수
function _multiply(uint a, uint b) private pure returns (uint) {
return a * b;
}
이더리움의 내장 해시 함수로 기본적으로 입력 스트링을 랜덤 256비트 16진수로 매핑
자료형의 일종으로 주소는 특정 유저(혹은 스마트 컨트랙트)가 소유
자료형의 일종으로 키-값 (key-value) 형태로 데이터를 저장하고 검색
// 금융 앱용으로, 유저의 계좌 잔액을 보유하는 uint를 저장한다:
mapping (address => uint) public accountBalance;
// 혹은 userID로 유저 이름을 저장/검색하는 데 매핑을 쓸 수도 있다
mapping (uint => string) userIdToName;
모든 함수에서 이용 가능한 특정 전역 변수 중의 하나로 현재 함수를 호출한 사람 (혹은 스마트 컨트랙트)의 주소를 가리킴
참고: 솔리디티에서 함수 실행은 항상 외부 호출자가 시작. 컨트랙트는 누군가가 컨트랙트의 함수를 호출할 때까지 블록체인 상에서 아무 것도 안 하므로 항상 msg.sender가 있어야 함.
특정 조건이 참이 아닐 때 함수가 에러 메시지를 발생하고 실행을 멈춤
require(keccak256(_name) == keccak256("Vitalik"));
contract Doge {
function catchphrase() public returns (string) {
return "So Wow CryptoDoge";
}
}
contract BabyDoge is Doge {
function anotherCatchphrase() public returns (string) {
return "Such Moon BabyDoge";
}
}
다수의 파일이 있고 어떤 파일을 다른 파일로 불러오고 싶을 때 사용
import "./someothercontract.sol";
Storage는 블록체인 상에 영구적으로 저장되는 변수
Memory는 임시적으로 저장되는 변수로, 컨트랙트 함수에 대한 외부 호출들이 일어나는 사이에 지워짐
internal은 함수가 정의된 컨트랙트를 상속하는 컨트랙트에서도 접근이 가능하다 점을 제외하면 private과 동일
external은 함수가 컨트랙트 바깥에서만 호출될 수 있고 컨트랙트 내의 다른 함수에 의해 호출될 수 없다는 점을 제외하면 public과 동일
다른 컨트랙트에서 public이나 external로 선언된 함수를 사용하기 위해 간단히 선언
c언어의 함수 원형과 비슷한 개념인 듯
contract NumberInterface {
function getNum(address _myAddress) public view returns (uint);
}
contract MyContract {
address NumberInterfaceAddress = 0xab38...
// ^ 이더리움상의 FavoriteNumber 컨트랙트 주소이다
NumberInterface numberContract = NumberInterface(NumberInterfaceAddress)
// 이제 `numberContract`는 다른 컨트랙트를 가리키고 있다.
function someFunction() public {
// 이제 `numberContract`가 가리키고 있는 컨트랙트에서 `getNum` 함수를 호출할 수 있다:
uint num = numberContract.getNum(msg.sender);
// ...그리고 여기서 `num`으로 무언가를 할 수 있다
}
}
function multipleReturns() internal returns(uint a, uint b, uint c) {
return (1, 2, 3);
}
// 혹은 단 하나의 값에만 관심이 있을 경우:
function getLastReturnValue() external {
uint c;
// 다른 필드는 빈칸으로 놓기만 하면 된다:
(,,c) = multipleReturns();
}
이더리움에 컨트랙트를 배포하고 나면, 컨트랙트는 변하지 않음(Immutable)
컨트랙트가 생성되면 컨트랙트의 생성자가 owner에 msg.sender(컨트랙트를 배포한 사람)를 대입
특정한 함수들에 대해서 오직 소유자만 접근할 수 있도록 제한 가능한 onlyOwner 제어자를 추가
새로운 소유자에게 해당 컨트랙트의 소유권을 옮길 수 있도록 함
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;
}
}
생성자(Constructor): function Ownable()
컨트랙트와 동일한 이름을 가진 생략할 수 있는 특별한 함수로 컨트랙트가 생성될 때 딱 한 번만 실행
함수 제어자(Function Modifier): modifier onlyOwner()
제어자는 다른 함수들에 대한 접근을 제어하기 위해 사용되는 일종의 유사 함수
보통 함수 실행 전의 요구사항 충족 여부를 확인하는 데에 사용
onlyOwner의 경우에는 접근을 제한해서 오직 컨트랙트의 소유자만 해당 함수를 실행할 수 있도록 하기 위해 사용
함수 제어자는 함수 정의 끝에 제어자의 이름을 붙임
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
contract MyContract is Ownable {
event LaughManiacally(string laughter);
// 아래 `onlyOwner`의 사용 방법을 잘 보게:
function likeABoss() external onlyOwner {
LaughManiacally("Muahahahaha");
}
}
likeABoss 함수를 호출하면, onlyOwner의 코드가 먼저 실행
onlyOwner의 _; 부분에서 likeABoss 함수로 되돌아가 해당 코드를 실행
제어자는 인수를 받을 수도 있음
// 사용자의 나이를 저장하기 위한 매핑
mapping (uint => uint) public age;
// 사용자가 특정 나이 이상인지 확인하는 제어자
modifier olderThan(uint _age, uint _userId) {
require (age[_userId] >= _age);
_;
}
// 차를 운전하기 위햐서는 16살 이상이어야 하네(적어도 미국에서는).
// `olderThan` 제어자를 인수와 함께 호출하려면 이렇게 하면 되네:
function driveCar(uint _userId) public olderThan(16, _userId) {
// 필요한 함수 내용들
}
솔리디티에서는 사용자들이 DApp의 함수를 실행할 때마다 가스라고 불리는 화폐를 지불
사용자는 이더(ETH, 이더리움의 화폐)를 이용해서 가스를 사기 때문에, 자네의 DApp 함수를 실행하려면 사용자들은 ETH를 소모
각각의 연산을 수행하는 데에 소모되는 컴퓨팅 자원의 양이 이 비용을 결정
=> 코드 최적화 중요
누군가가 무한 반복문을 써서 네트워크를 방해하거나, 자원 소모가 큰 연산을 써서 네트워크 자원을 모두 사용하지 못하도록 연산 처리에 비용이 들도록 설계
view 함수는 사용자에 의해 외부에서 호출되었을 때 가스를 전혀 소모하지 않음
참고: 만약 view 함수가 동일 컨트랙트 내에 있는, view 함수가 아닌 다른 함수에서 내부적으로 호출될 경우,가스 소모
배열에 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;
}
now 변수를 쓰면 현재의 유닉스 타임스탬프(1970년 1월 1일부터 지금까지의 초 단위 합) 값을 얻음
seconds, minutes, hours, days, weeks, years 같은 시간 단위들은 그에 해당하는 길이 만큼의 초 단위 uint 숫자로 변환됨
private 또는 internal 함수에 인수로 구조체의 storage 포인터를 전달할 수 있음
function getArray() external pure returns(uint[]) {
// 메모리에 길이 3의 새로운 배열을 생성한다.
uint[] memory values = new uint[](3);
// 여기에 특정한 값들을 넣는다.
values.push(1);
values.push(2);
values.push(3);
// 해당 배열을 반환한다.
return values;
}
function _doStuff(Zombie storage _zombie) internal {
// _zombie로 할 수 있는 것들을 처리
}
아무래도 크립토좀비 사이트에선 정답을 바로 확인할 수 있다 보니 좀만 힘들어도 바로 정답 보고 베끼게 되더라...... 개념 정리하면서 강의 열심히 듣되, 다른 프로그램도 만들어 봐야 할 듯
오히려 문제 설명 과정에서 배울 점이 많았는데, 내 프로젝트에 필요한 요소들을 적재적소에 배치하고 디테일하게 수정할 점을 짚어 주는 지시사항이 명확해서 좋았음
크립토좀비 프로그램도 내가 공부하려고 하다 보니까 힘들지, 가벼운 마음으로 보면 재미있게 솔리디티를 배울 수 있는 방법이라고 생각함