Solidity Study : Introduction to Smart Contracts/스마트 컨트랙트 입문

출처 : https://solidity.readthedocs.io/en/develop/introduction-to-smart-contracts.html

스터디 하면서 docs를 번역해 보고 있습니다.

 

간단한 스마트 컨트랙트

간단한 예제부터 시작하도록 한다. 지금 당장 모든 예제를 이해할 필요는 없다. 나중에 모두 설명할 것이다.

스토리지

첫 줄은 간단하게 소스코드가 솔리디티 0.4.0 버전을 위해서 작성되었다는 걸 말한다. 혹은 다른 더 새로운 버전도 기능적인 부분에서는 다르지 않다 (0.5.0 버전 미만 까지). 이 것은 컨트랙트가 새로운 컴파일러에서 갑자기 예상과 다른 결과가 나오지 않도록 보장한다. pragma는 일반적으로 컴파일러가 소스코드를 다루는 방법을 지시하는데 이렇게 호출된다 (예 : pragma once).

솔리디티에서 컨트랙트는 이더리움 블록체인의 특정한 주소에 있는 코드(기능)와 데이터(상태)의 모임이다. uint storedData;은  storedData라는 uint(unsigned integer of 256 bits) 상태 변수를 선언한다. 컨트랙트는 데이터베이스를 관리할 때 함수의 호출로 쿼리하거나 변경할 수 있는 하나의 슬롯으로 생각할 수 있다. 이더리움에서는, 이 것은 항상 컨트랙트를 소유하고 있다. 그리고 이 경우에 set과 get이라는 함수는 변수의 값을 수정하거나 검색할 수 있다.

상태변수에 접근하기 위해서 다른 언어들 처럼 this.가 필요하지 않다.

이 컨트랙트는 전 세계 누구든지 접근 가능한 숫자 하나를 누군가 저장할 때, 이를 보호하는 방안이 없더라도 기능을 구현하게 하는 것을 제외하고는 아직 많은 것을 하지는 않는다 (이더리움으로 구축된 인프라로 인하여). 물론, 누군가 set을 다시 호출하여 다른 값으로 변경할 수도 있다. 하지만  당신의 숫자는 블록체인의 기록물에 여전히 저장되어 있다. 나중에는 접근제한을 적용하여 당신만 숫자를 변경할 수 있도록 하는 방법을 살펴볼 것이다.

 

노트

모든 식별자 (컨트랙트 이름, 함수 이름, 변수 이름 등)은 아스키 문자로 제한된다. 문자열 변수에는 UTF-8로 인코딩 된 데이터를 저장할 수 있다.

경고

비슷하게 보이는(심지어 같은) 문자들이 다른 코드를 가질 수 있고, 다른 바이트 배열로 인코딩 될 수 있으므로 유니코드 텍스트 사용을 조심

 

보조 통화 예제

다음의 컨트랙트는 간단한 형태의 암호화폐를 구현하는 것이다. 난데없이 코인을 제작할 수 있겠지만, 컨트랙트를 만든 사람 만이 그렇게 할 수 있다 (다른 발행 계획을 구현하는 것은 간단하다). 더욱이, 누구나 회원가입 없이 코인을 서로에게 보낼 수 있다. – 당신에게 필요한 것은 이더리움 키페어 뿐이다.

 

이 컨트랙트는 어떤 새로운 개념을 소개하고 있는데, 하나씩 알아보자.

address public minter; 는 public 접근성을 가지는 address 타입의 상태변수를 선언한다.  address 타입은 어떠한 연산도 허용하지 않는 160-bit의 값이다. 이 것은 컨트랙트의 어드레스나 외부 사람들이 소유한 키페어를 저장하는데 적합하다. public 키워드는 당신이 현재의 상태변수 값에 접근할 수 있도록 함수를 자동으로 생성한다. 이 키워드 없이는 다른 컨트랙트들이 값에 접근할 수 없다. 함수는 아래와 같다.

물론 정확히 이 함수를 추가하는 것은 작동하지 않을 것이다. 왜냐하면 같은 이름을 가진 함수와 상태변수를 가지고 있을 것이기 때문이다. 하지만 희망적이게도, 당신은 한 가지 사실을 알 수 있다. 컴파일러는 당신을 위해서 분석해줄 것이다.

다음 줄  mapping (address => uint) public balances; 또한 public 접근성을 가지는 상태변수를 만든다. 그러나 이건 더 복잡한 데이터형이다. 이 타입은 address들은 uint로 매핑한다. 매핑은 모든 가능한 키가 존재하고 0으로 표현되는 바이트 값으로 매핑되는 가상으로 초기화된  hash tables 로 볼 수 있다. 하지만 매핑의 모든 키와 값들의 리스트를 얻을 수 없기 때문에 앞의 비유는 그렇게 다 맞지는 않다. 따라서 매핑에 추가한 내용을 기억하거나(또는 더 좋은 경우에선 모든 리스트를 저장하거나 더 고급의 데이터 타입을 사용하거나) 이 정보가 필요하지 않은 환경에서 사용해야 한다. public 키워드로 만들어진 getter함수는 이 경우엔 조금 더 복잡하다. 대략 적으로 다음과 같다.

보시다시피, 단일 계정의 잔액을 쉽게 조회할 수 있다.

event Sent(address from, address to, uint amount); 는 send함수의 마지막 줄에서 발현되는 이른바 “event”라는 것을 선언한다. 사용자 환경은 블록체인에서 발현되는 이러한 이벤트 들을 많은 비용 없이 받을 수 있다. 이 것이 발현되면, 청취자는 트랜섹션을 쉽게 추적할 수 있는 from, toamount등의 정보 또한 수신한다. 이러한 이벤트를 청취하기 위해서 아래를 사용할  수 있다.

자동으로 생성된 balances함수가 어떻게 사용자 환경으로 부터 호출되는지 확인하자

특별한 함수인 Coin 은 컨트랙트가 생성될 때의 생성자이고 이후에는 호출될 수 없다. 이 것은 다음과 같은 컨트랙트를 생성한 사람의 주소를 영구적으로 보관한다. tx와 블록이 함께 있는 메시지는 블록체인에 접근할 수 있도록 하는 약간의 속성을 포함한 마법의 전역 변수이다. msg.sender 은 항상 현재(외부)의 함수가 호출된 곳의 주소다.

마지막으로, 실제로 컨트랙트가 끝나고 사용자들과 컨트랙트들이 호출할 수 있는 함수는 mint 와 send. 만약 mint 가 컨트랙트를 생성한 계정을 제외한 누군가에 의해 호출된다면, 아무런 일도 일어나지 않는다. 반면에, send 는 누군가(미리 약간의 코인을 가지고 있는)가 다른 누군가에게 전송하는데 사용될 수 있다. 만약 이 컨트랙트를 코인을 어떠한 주소에 보내는데 사용한다면, 코인을 보낸 사실과 변경된 잔액만 특정한 코인 컨트랙트 저장소에 저장되기 때문에 블록체인 탐색기에서는 그 주소를 볼 수 없다. 이러한 이벤트를 사용하면 새로운 코인의 트랜섹션과 잔액을 추적하는 블록체인 탐색기를 만들기가 비교적 쉽다.

 

Blockchain Basics

Blockchains as a concept are not too hard to understand for programmers. The reason is that most of the complications (mining, hashing, elliptic-curve cryptography, peer-to-peer networks, etc.) are just there to provide a certain set of features and promises. Once you accept these features as given, you do not have to worry about the underlying technology – or do you have to know how Amazon’s AWS works internally in order to use it?

 

Transactions

A blockchain is a globally shared, transactional database. This means that everyone can read entries in the database just by participating in the network. If you want to change something in the database, you have to create a so-called transaction which has to be accepted by all others. The word transaction implies that the change you want to make (assume you want to change two values at the same time) is either not done at all or completely applied. Furthermore, while your transaction is applied to the database, no other transaction can alter it.

As an example, imagine a table that lists the balances of all accounts in an electronic currency. If a transfer from one account to another is requested, the transactional nature of the database ensures that if the amount is subtracted from one account, it is always added to the other account. If due to whatever reason, adding the amount to the target account is not possible, the source account is also not modified.

Furthermore, a transaction is always cryptographically signed by the sender (creator). This makes it straightforward to guard access to specific modifications of the database. In the example of the electronic currency, a simple check ensures that only the person holding the keys to the account can transfer money from it.

 

Blocks

One major obstacle to overcome is what, in Bitcoin terms, is called a “double-spend attack”: What happens if two transactions exist in the network that both want to empty an account, a so-called conflict?

The abstract answer to this is that you do not have to care. An order of the transactions will be selected for you, the transactions will be bundled into what is called a “block” and then they will be executed and distributed among all participating nodes. If two transactions contradict each other, the one that ends up being second will be rejected and not become part of the block.

These blocks form a linear sequence in time and that is where the word “blockchain” derives from. Blocks are added to the chain in rather regular intervals – for Ethereum this is roughly every 17 seconds.

As part of the “order selection mechanism” (which is called “mining”) it may happen that blocks are reverted from time to time, but only at the “tip” of the chain. The more blocks that are added on top, the less likely it is. So it might be that your transactions are reverted and even removed from the blockchain, but the longer you wait, the less likely it will be.

 

 

The Ethereum Virtual Machine

Overview

The Ethereum Virtual Machine or EVM is the runtime environment for smart contracts in Ethereum. It is not only sandboxed but actually completely isolated, which means that code running inside the EVM has no access to network, filesystem or other processes. Smart contracts even have limited access to other smart contracts.

 

Accounts

There are two kinds of accounts in Ethereum which share the same address space: External accounts that are controlled by public-private key pairs (i.e. humans) and contract accounts which are controlled by the code stored together with the account.

The address of an external account is determined from the public key while the address of a contract is determined at the time the contract is created (it is derived from the creator address and the number of transactions sent from that address, the so-called “nonce”).

Regardless of whether or not the account stores code, the two types are treated equally by the EVM.

Every account has a persistent key-value store mapping 256-bit words to 256-bit words called storage.

Furthermore, every account has a balance in Ether (in “Wei” to be exact) which can be modified by sending transactions that include Ether.

 

Transactions

A transaction is a message that is sent from one account to another account (which might be the same or the special zero-account, see below). It can include binary data (its payload) and Ether.

If the target account contains code, that code is executed and the payload is provided as input data.

If the target account is the zero-account (the account with the address 0), the transaction creates a new contract. As already mentioned, the address of that contract is not the zero address but an address derived from the sender and its number of transactions sent (the “nonce”). The payload of such a contract creation transaction is taken to be EVM bytecode and executed. The output of this execution is permanently stored as the code of the contract. This means that in order to create a contract, you do not send the actual code of the contract, but in fact code that returns that code.

 

Gas

Upon creation, each transaction is charged with a certain amount of gas, whose purpose is to limit the amount of work that is needed to execute the transaction and to pay for this execution. While the EVM executes the transaction, the gas is gradually depleted according to specific rules.

The gas price is a value set by the creator of the transaction, who has to pay gas_price * gas up front from the sending account. If some gas is left after the execution, it is refunded in the same way.

If the gas is used up at any point (i.e. it is negative), an out-of-gas exception is triggered, which reverts all modifications made to the state in the current call frame.

 

Storage, Memory and the Stack

Each account has a persistent memory area which is called storage. Storage is a key-value store that maps 256-bit words to 256-bit words. It is not possible to enumerate storage from within a contract and it is comparatively costly to read and even more so, to modify storage. A contract can neither read nor write to any storage apart from its own.

The second memory area is called memory, of which a contract obtains a freshly cleared instance for each message call. Memory is linear and can be addressed at byte level, but reads are limited to a width of 256 bits, while writes can be either 8 bits or 256 bits wide. Memory is expanded by a word (256-bit), when accessing (either reading or writing) a previously untouched memory word (ie. any offset within a word). At the time of expansion, the cost in gas must be paid. Memory is more costly the larger it grows (it scales quadratically).

The EVM is not a register machine but a stack machine, so all computations are performed on an area called the stack. It has a maximum size of 1024 elements and contains words of 256 bits. Access to the stack is limited to the top end in the following way: It is possible to copy one of the topmost 16 elements to the top of the stack or swap the topmost element with one of the 16 elements below it. All other operations take the topmost two (or one, or more, depending on the operation) elements from the stack and push the result onto the stack. Of course it is possible to move stack elements to storage or memory, but it is not possible to just access arbitrary elements deeper in the stack without first removing the top of the stack.

 

Instruction Set

The instruction set of the EVM is kept minimal in order to avoid incorrect implementations which could cause consensus problems. All instructions operate on the basic data type, 256-bit words. The usual arithmetic, bit, logical and comparison operations are present. Conditional and unconditional jumps are possible. Furthermore, contracts can access relevant properties of the current block like its number and timestamp.

 

Message Calls

Contracts can call other contracts or send Ether to non-contract accounts by the means of message calls. Message calls are similar to transactions, in that they have a source, a target, data payload, Ether, gas and return data. In fact, every transaction consists of a top-level message call which in turn can create further message calls.

A contract can decide how much of its remaining gas should be sent with the inner message call and how much it wants to retain. If an out-of-gas exception happens in the inner call (or any other exception), this will be signalled by an error value put onto the stack. In this case, only the gas sent together with the call is used up. In Solidity, the calling contract causes a manual exception by default in such situations, so that exceptions “bubble up” the call stack.

As already said, the called contract (which can be the same as the caller) will receive a freshly cleared instance of memory and has access to the call payload – which will be provided in a separate area called the calldata. After it has finished execution, it can return data which will be stored at a location in the caller’s memory preallocated by the caller.

Calls are limited to a depth of 1024, which means that for more complex operations, loops should be preferred over recursive calls.

 

Delegatecall / Callcode and Libraries

There exists a special variant of a message call, named delegatecall which is identical to a message call apart from the fact that the code at the target address is executed in the context of the calling contract and msg.sender and msg.value do not change their values.

This means that a contract can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract, only the code is taken from the called address.

This makes it possible to implement the “library” feature in Solidity: Reusable library code that can be applied to a contract’s storage, e.g. in order to implement a complex data structure.

 

Logs

It is possible to store data in a specially indexed data structure that maps all the way up to the block level. This feature called logs is used by Solidity in order to implement events. Contracts cannot access log data after it has been created, but they can be efficiently accessed from outside the blockchain. Since some part of the log data is stored in bloom filters, it is possible to search for this data in an efficient and cryptographically secure way, so network peers that do not download the whole blockchain (“light clients”) can still find these logs.

 

Create

Contracts can even create other contracts using a special opcode (i.e. they do not simply call the zero address). The only difference between these create calls and normal message calls is that the payload data is executed and the result stored as code and the caller / creator receives the address of the new contract on the stack.

 

Self-destruct

The only possibility that code is removed from the blockchain is when a contract at that address performs the selfdestruct operation. The remaining Ether stored at that address is sent to a designated target and then the storage and code is removed from the state.

Warning

Even if a contract’s code does not contain a call to selfdestruct, it can still perform that operation using delegatecall or callcode.

Note

The pruning of old contracts may or may not be implemented by Ethereum clients. Additionally, archive nodes could choose to keep the contract storage and code indefinitely.

Note

Currently external accounts cannot be removed from the state.

1,505 Comments