스마트 컨트랙트 개발 시작하기
IOST 스마트 컨트랙트 기본
블록체인은 네트워크 전체에 동기화되는 상태 머신으로 추상화할 수 있습니다. 스마트 컨트랙트는 블록체인 시스템에서 실행되며 트랜잭션을 통해 상태 머신의 상태를 변경하는 코드입니다. 블록체인의 특성상 스마트 컨트랙트의 호출은 직렬 처리되며 전역적으로 일관성이 보장됩니다.
스마트 컨트랙트는 블록 안에서 트랜잭션을 수신·실행하여 컨트랙트 내부 변수를 유지하고 불가역 증명을 생성합니다.
IOST는 다중 언어 스마트 컨트랙트를 지원합니다. 현재는 v8 엔진으로 JavaScript(ES6)를 공개했으며, 고성능 트랜잭션 처리를 위한 네이티브 golang VM 모듈도 있지만 현재는 시스템 컨트랙트에만 사용됩니다.
IOST 스마트 컨트랙트는 컨트랙트 코드와 ABI를 기술하는 JSON 파일로 구성되며, 자체 네임스페이스와 격리된 스토리지를 가집니다. 외부에서는 그 스토리지 내용을 읽을 수만 있습니다.
모든 트랜잭션에는 여러 개의 트랜잭션 액션이 포함되며, 각 액션은 ABI 호출입니다. 모든 트랜잭션은 체인상에서 엄격히 직렬로 정렬되어 이중 지불 공격을 방지합니다.
키워드
| 키워드 | 설명 |
|---|---|
| ABI | 스마트 컨트랙트 인터페이스. 외부에서는 선언된 인터페이스를 통해서만 호출 가능 |
| Tx | 트랜잭션. 블록체인 상의 상태는 반드시 tx를 제출하여 변경해야 하며, tx는 블록에 패킹됩니다 |
디버그 환경 구성
iwallet과 로컬 테스트 노드 준비
스마트 컨트랙트 개발과 배포에는 iwallet이 필요합니다. 동시에 로컬 테스트 노드 실행을 함께 사용하면 디버깅이 편리합니다.
iwallet에 초기 계정 admin import
계정 키 스토어 파일을 사용하는 방법은 두 가지입니다. 편한 쪽을 선택하세요.
방법 1: 기본 경로로 키 스토어 파일 import
테스트를 진행하려면 iwallet에 계정을 import해야 합니다. 로컬 테스트 노드의 'admin' 계정을 import할 수 있습니다.
iwallet account import admin 2yquS3ySrGWPEKywCPzX4RTJugqRh7kJSo5aehsLYPEWkUxBWA39oMrZ7ZxuM4fgyXYs2cPwh5n8aNNpH5x2VyK1
이 명령 이후 키 스토어 json 파일이 ~/.iwallet/admin.json 에 기록됩니다. 이후 'iwallet --account admin' 명령은 ~/.iwallet 폴더에서 해당 계정의 키 스토어 json을 찾아 사용합니다.
방법 2: 키 스토어 파일 경로를 직접 지정
iwallet account import admin 2yquS3ySrGWPEKywCPzX4RTJugqRh7kJSo5aehsLYPEWkUxBWA39oMrZ7ZxuM4fgyXYs2cPwh5n8aNNpH5x2VyK1 --key_file admin.json
# 이 파일 경로를 기억해 두세요. 이후 명령에서 iwallet --key_file admin.json 형식으로 전달합니다.
로컬 디버그 체인용 admin 키 파일이 이미 제공되어 있으므로 'iwallet --key_file config/dev_admin_key.json' 형태로 직접 사용할 수도 있습니다.
Hello world
코드 준비
먼저 JavaScript 클래스를 준비합니다. 예: HelloWorld.js
class HelloWorld {
init() {} // 배포 시 호출될 init 함수를 반드시 제공해야 합니다.
hello(someone) {
return "hello, "+ someone
}
}
module.exports = HelloWorld;
이 스마트 컨트랙트는 입력을 받아 hello, + 입력값을 반환하는 인터페이스를 가집니다. 이 인터페이스를 외부에서 호출할 수 있도록 abi 파일을 준비해야 합니다. 예: HelloWorld.abi
iwallet compile helloworld.js
vim helloworld.js.abi
{
"lang": "javascript",
"version": "1.0.0",
"abi": [
{
"name": "hello",
"args": [
"string"
],
"amountLimit": [],
"description": ""
}
]
}
abi의 name 필드는 js 함수 이름과 일치하며, args 리스트로 기본적인 타입 검사를 합니다. string, number, bool 세 가지 타입만 사용할 것을 권장합니다.
로컬 테스트 노드에 배포
스마트 컨트랙트 배포
# `go-iost` 폴더에서 실행하세요
# chain_id 1020 = 로컬 체인
# chain_id 1024 = 메인넷
# chain_id 1023 = 테스트넷
iwallet \
--server localhost:30002 \
--account admin \
--key_file config/dev_admin_key.json \
--chain_id 1020 \
publish helloworld.js helloworld.js.abi
샘플 출력
Sending transaction...
Transaction has been sent.
The transaction hash is: 2xC6ziTqXaat7dsrya9pHog6NEEAMgBMKWcMv5YNDEpa
Checking transaction receipt...
SUCCESS!
The contract id is: Contract2xC6ziTqXaat7dsrya9pHog6NEEAMgBMKWcMv5YNDEpa
ABI 호출 테스트
iwallet \
--server localhost:30002 \
--account admin \
call "Contract96YFqvomoAnX6Zyj993fkv29D2HVfm8cjGhCEM1ymXGf" "hello" '["developer"]' # 컨트랙트 id는 실제 받은 값으로 바꿔주세요
출력
Sending transaction...
Transaction has been sent.
The transaction hash is: CzQi1ro44E6ysVq6o6c6UEqYNrPbN7HruAjewoGfRTBy
Checking transaction receipt...
SUCCESS!
이후 언제든 다음 명령으로 TxReceipt를 얻을 수 있습니다.
iwallet receipt GTUmtpWPdPMVvJdsVf8AiEPy9EzCBUwUCim9gqKjvFLc
http로도 조회 가능합니다.
curl -X GET \
http://localhost:30001/getTxReceiptByTxHash/GTUmtpWPdPMVvJdsVf8AiEPy9EzCBUwUCim9gqKjvFLc
이 호출은 IOST에 영구적으로 기록되며 변경할 수 없다고 보아도 무방합니다.
IDE
컨트랙트 개발에는 온라인 IDE 를 사용할 수 있습니다. 먼저 iwallet Pro 를 설치하고 계정을 import하여 리소스를 구매한 뒤, IDE에서 컨트랙트를 개발·배포·디버그할 수 있습니다.
스마트 컨트랙트 상태 저장
스마트 컨트랙트의 output 사용(UTXO 개념과 유사)은 불편하므로 IOST는 이 방식을 사용하지 않으며, 따라서 IOST는 TxReceipt의 각 필드에 대한 인덱스를 제공하지 않고 스마트 컨트랙트도 특정 TxReceipt에 접근할 수 없습니다. 블록체인 상태 머신을 유지하기 위해 별도의 블록체인 상태 데이터베이스를 사용합니다.
이 데이터베이스는 순수 K-V 저장소로, key와 value의 타입은 string입니다. 각 스마트 컨트랙트는 독립된 네임스페이스를 갖습니다. 스마트 컨트랙트는 다른 컨트랙트의 상태 데이터를 읽을 수는 있지만, 자신의 필드만 쓸 수 있습니다.
전체 API는 여기에 있습니다: Blockchain API
코딩
class Test {
init() {
storage.put("value1", "foobar")
}
get() {
return storage.get("value1")
}
change(someone) {
storage.put("value1", someone)
}
}
module.exports = Test;
상태 저장 사용하기
코드를 배포한 뒤 다음 방법으로 저장된 값을 조회할 수 있습니다.
curl -X POST \
http://localhost:30001/getContractStorage \
-d '{
"id": "Contract5bxTBndRrNjMJqJdRwiC9MVtfp6Z2LFFDp3AEjceHo2e",
"key": "value1",
"by_longest_chain": true
}'
이 POST 요청은 다음과 같은 json을 반환합니다.
{
"data": "foobar"
}
change를 호출하여 이 값을 변경할 수 있습니다.
iwallet \
--server localhost:30002 \
--account admin \
call "Contract5bxTBndRrNjMJqJdRwiC9MVtfp6Z2LFFDp3AEjceHo2e" "change" '["foobaz"]'
권한 제어와 스마트 컨트랙트 실패
권한 제어의 기본은 다음과 같습니다.
예시
if (!blockchain.requireAuth("someone", "active")) {
throw "require auth error" // throw가 가상 머신으로 던져져 실패가 발생합니다
}
다음 점에 유의해야 합니다.
- requireAuth 자체는 스마트 컨트랙트의 실행을 중단시키지 않고 bool 값만 반환하므로 직접 판단해야 합니다.
- requireAuth(tx.publisher, "active")는 항상 true를 반환합니다.
throw가 발생하면 트랜잭션 실행은 실패하고 스마트 컨트랙트 호출은 완전히 롤백되지만, 트랜잭션을 발행한 사용자에게는 가스 비용이 차감됩니다(롤백되므로 ram 비용은 발생하지 않습니다).
다음의 간단한 테스트로 실패하는 트랜잭션을 관찰할 수 있습니다.
iwallet \
--server localhost:30002 \
--account admin \
call "token.iost" "transfer" '["iost","someone","me","10","this is steal"]'
결과는 다음과 같습니다.
Sending transaction...
Transaction has been sent.
The transaction hash is: 6KY4h4gKHFwuovXZJEDzvPtN9YYcJ5kUFHLf84gktYYu
Checking transaction receipt...
ERROR: running action Action{Contract: token.iost, ActionName: transfer, Data: ["iost","someone","me","10","this is st... error: transaction has no permission
디버깅과 컨트랙트 로그
먼저 위 설명대로 로컬 노드를 시작합니다. docker를 사용한다면 다음 명령으로 로그를 출력할 수 있습니다.
docker ps -f <container>
이 시점에 코드에서 console.log()로 필요한 로그를 추가할 수 있습니다. console.log()로 전달된 인자는 서버 프로세스의 stdout에 출력됩니다. config/iserver.yml의 'log -> enablecontractlog'가 true인지 확인하세요.
ABI 인터페이스 정의
IOST 스마트 컨트랙트는 ABI를 통해 네트워크와 상호작용합니다.
ABI는 JSON으로 정의된 정보로, 이름과 파라미터 타입 등을 포함합니다. 지원되는 기본 타입은 string, number, bool 입니다.
더 복잡한 데이터 구조는 JSON 문자열로 파싱할 수 있습니다. 스마트 컨트랙트의 함수를 호출할 때는 ABI에 명시된 파라미터 타입을 엄격히 따라야 합니다. 그렇지 않으면 실행이 중단되고 트랜잭션 비용이 발생합니다.
// luckybet.js.abi 예시
{
"lang": "javascript",
"version": "1.0.0",
"abi": [
{
"name": "bet",
"args": [
"string",
"number",
"number",
"number"
],
"amountLimit": [
{
"token": "iost",
"val": "unlimited"
},
{
"token": "sometoken",
"val": "1000"
}
]
}
]
}
amountLimit은 이 ABI가 소비할 수 있는 토큰의 양을 나타냅니다. 위 예시에서 이 ABI는 iost 토큰을 무제한 소비할 수 있고 sometoken을 최대 1000까지 소비할 수 있으며, 그 외의 토큰을 소비하려 하면 트랜잭션이 롤백됩니다.
Blockchain API
Blockchain API 가 제공됩니다. 블록체인과 상호작용하는 데 사용합니다.
리소스 제한
컨트랙트 실행에는 gas가 소모됩니다. gas는 이 표 에 따라 부과됩니다. 모든 트랜잭션은 200ms 내에 실행을 마쳐야 하며, 그렇지 않으면 강제 종료되고 롤백됩니다.
다른 컨트랙트 호출
스마트 컨트랙트 안에서 blockchain.call()을 사용해 다른 ABI 인터페이스를 호출하고 반환값을 받을 수 있습니다.
스마트 컨트랙트 간 호출은 서명 권한을 전달할 수 있습니다. 예를 들어 A.a가 blockchain.callWithAuth를 사용해 B.b를 호출하면, A.a 호출 시 사용자가 부여한 권한이 B.b로도 전달됩니다. 반면 blockchain.call을 사용하면 권한이 전달되지 않습니다.
스마트 컨트랙트는 호출 스택을 검사하여 "누가 이 ABI를 호출했는가?" 같은 질문에 답할 수 있으므로 특정 작업을 구현할 수 있습니다.
시스템 컨트랙트
Econ Contract 는 ram/gas 관리에 사용합니다. Token Contract 는 토큰 발행/전송에 사용합니다. System Contract 는 투표, 계정, 컨트랙트 관련 컨트랙트들을 포함합니다.
컨트랙트 업그레이드
can_update()가 정의된 스마트 컨트랙트는 업그레이드할 수 있습니다. 자세한 내용은 여기 참고.
TxReceipt
실행이 끝나면 스마트 컨트랙트는 TxReceipt를 블록에 생성하고 합의 과정을 거칩니다. 자세한 내용은 여기 참고.