Ethereum Oracle Contracts: Solidity 코드 기능
게시 됨: 2022-03-11이 3부작의 첫 번째 부분에서 우리는 Oracle과의 간단한 계약을 제공하는 약간의 자습서를 살펴보았습니다. 설정(트러플 사용), 코드 컴파일, 테스트 네트워크에 배포, 실행 및 디버깅의 메커니즘과 프로세스가 설명되었습니다. 그러나 코드의 많은 세부 사항은 손으로 휘두르는 방식으로 생략되었습니다. 이제 약속한 대로 Solidity 스마트 계약 개발에 고유하고 이 특정 계약-오라클 시나리오에 고유한 언어 기능 중 일부를 살펴보겠습니다. 모든 세부 사항을 공들여 볼 수는 없지만(원하는 경우 추가 연구에서 이를 남겨 둘 것입니다), 코드의 가장 인상적이고 가장 흥미롭고 가장 중요한 기능을 설명하려고 노력할 것입니다.
이를 용이하게 하려면 자신의 프로젝트 버전(있는 경우)을 열거나 참조용으로 편리한 코드를 사용하는 것이 좋습니다.
이 시점의 전체 코드는 https://github.com/jrkosinski/oracle-example/tree/part2-step1에서 찾을 수 있습니다.
이더리움과 솔리디티
Solidity는 사용 가능한 유일한 스마트 계약 개발 언어는 아니지만 Ethereum 스마트 계약에 대해 일반적으로 가장 일반적이고 가장 많이 사용되는 언어라고 말할 수 있을 만큼 안전하다고 생각합니다. 확실히 그것은 이 글을 쓰는 시점에서 가장 인기 있는 지원과 정보를 가지고 있는 것입니다.
Solidity는 객체 지향적이고 튜링 완전합니다. 즉, 스마트 계약 프로그래밍이 일반적인 let's-do-thing 해킹과 상당히 다른 느낌을 주는 내장된(그리고 완전히 의도적인) 제한을 빨리 깨닫게 될 것입니다.
솔리디티 버전
다음은 모든 Solidity 코드 시의 첫 번째 줄입니다.
pragma solidity ^0.4.17;
아직 초기 단계인 Solidity가 빠르게 변화하고 발전하고 있기 때문에 표시되는 버전 번호가 다를 것입니다. 버전 0.4.17은 예제에서 사용한 버전입니다. 이 발행 당시의 최신 버전은 0.4.25입니다.
현재 이 글을 읽고 있는 최신 버전은 완전히 다른 것일 수 있습니다. 현재 논의할 Solidity용으로 많은 멋진 기능이 작업 중이거나 적어도 계획되어 있습니다.
다음은 다양한 Solidity 버전에 대한 개요입니다.
전문가 팁: 다음 과 같이 버전 범위를 지정할 수도 있습니다(이 작업이 자주 수행되지는 않지만).
pragma solidity >=0.4.16 <0.6.0;
Solidity 프로그래밍 언어 기능
Solidity에는 대부분의 현대 프로그래머에게 친숙한 언어 기능과 독특하고 (적어도 저에게는) 특이한 언어 기능이 많이 있습니다. C++, Python 및 JavaScript에서 영감을 얻었다고 합니다. 이 모든 것이 저에게 개인적으로 잘 알려져 있지만 Solidity는 이러한 언어와 상당히 다른 것 같습니다.
계약
.sol 파일은 코드의 기본 단위입니다. BoxingOracle.sol에서 9번째 줄을 확인하세요.
contract BoxingOracle is Ownable {
클래스가 객체지향 언어에서 논리의 기본 단위인 것처럼, 계약은 솔리디티에서 논리의 기본 단위입니다. 계약이 Solidity의 "클래스"라고 말하기 위해 지금은 단순화하는 것으로 충분합니다(객체 지향 프로그래머의 경우 이것은 쉬운 도약입니다).
계승
Solidity 계약은 상속을 완벽하게 지원하며 예상대로 작동합니다. 개인 계약 구성원은 상속되지 않지만 보호 및 공개 구성원은 상속됩니다. 예상대로 오버로딩과 다형성이 지원됩니다.
contract BoxingOracle is Ownable {
위의 문장에서 "is" 키워드는 상속을 의미합니다. BoxingOracle은 Ownerable에서 상속합니다. 다중 상속은 Solidity에서도 지원됩니다. 다중 상속은 다음과 같이 쉼표로 구분된 클래스 이름 목록으로 표시됩니다.
contract Child is ParentA, ParentB, ParentC { …
(내 생각에) 상속 모델을 구조화할 때 지나치게 복잡해지는 것은 좋은 생각이 아니지만, 여기에 소위 다이아몬드 문제와 관련하여 Solidity에 대한 흥미로운 기사가 있습니다.
열거
열거형은 Solidity에서 지원됩니다.
enum MatchOutcome { Pending, //match has not been fought to decision Underway, //match has started & is underway Draw, //anything other than a clear winner (eg, cancelled) Decided //index of participant who is the winner }
예상한 대로(익숙한 언어와 다르지 않음) 각 열거형 값에는 0부터 시작하는 정수 값이 할당됩니다. Solidity 문서에 명시된 대로 열거형 값은 모든 정수 유형(예: uint, uint16, uint32, 등) 그러나 암시적 변환은 허용되지 않습니다. 즉, 명시적으로 캐스팅해야 합니다(예: uint로).
Solidity 문서: Enums Enums 튜토리얼
구조체
구조체는 열거형과 같이 사용자 정의 데이터 유형을 생성하는 또 다른 방법입니다. 구조체는 모든 C/C++ 기초 코더와 저와 같은 노인들에게 친숙합니다. BoxingOracle.sol의 17행에 있는 구조체의 예:
//defines a match along with its outcome struct Match { bytes32 id; string name; string participants; uint8 participantCount; uint date; MatchOutcome outcome; int8 winner; }
모든 오래된 C 프로그래머에 대한 참고 사항: Solidity의 Struct "패킹"은 중요하지만 몇 가지 규칙과 주의 사항이 있습니다. C에서와 동일하게 작동한다고 반드시 가정하지 마십시오. 문서를 확인하고 상황을 파악하여 주어진 경우에 포장이 도움이 될 것인지 확인하십시오.
Solidity 구조체 패킹
일단 생성되면 코드에서 기본 데이터 유형으로 구조체를 처리할 수 있습니다. 다음은 위에서 생성한 구조체 유형의 "인스턴스화" 구문의 예입니다.
Match match = Match(id, "A vs. B", "A|B", 2, block.timestamp, MatchOutcome.Pending, 1);
Solidity의 데이터 유형
이것은 Solidity의 데이터 유형에 대한 매우 기본적인 주제로 우리를 인도합니다. Solidity는 어떤 데이터 유형을 지원합니까? Solidity는 정적으로 유형이 지정되며 이 작성 시 데이터 유형을 명시적으로 선언하고 변수에 바인딩해야 합니다.
Solidity 데이터 유형
부울
부울 유형은 bool 이라는 이름으로 지원되며 값은 true 또는 false 입니다.
숫자 유형
정수 유형은 int8/uint8에서 int256/uint256(각각 8비트 정수에서 256비트 정수)까지 부호 있는 및 부호 없는 정수 유형이 모두 지원됩니다. uint 유형은 uint256의 약어입니다(마찬가지로 int는 int256의 약자입니다).
특히 부동 소수점 유형은 지원 되지 않습니다 . 왜 안 돼? 음, 우선, 금전적 가치를 다룰 때 부동 소수점 변수는 나쁜 생각으로 잘 알려져 있습니다(물론 일반적으로). 가치가 허공에 손실될 수 있기 때문입니다. 에테르 값은 wei로 표시되며 이는 에테르의 1/1,000,000,000,000,000,000분의 1이며 모든 목적에 충분한 정밀도여야 합니다. 에테르를 더 작은 부분으로 분해할 수 없습니다.
고정 소수점 값은 현재 부분적으로 지원됩니다. Solidity 문서에 따르면: “고정 소수점 수는 아직 Solidity에서 완전히 지원되지 않습니다. 선언할 수는 있지만 할당하거나 할당할 수는 없습니다."
https://hackernoon.com/a-note-on-numbers-in-ethereum-and-javascript-3e6ac3b2fad9
참고: 대부분의 경우 변수의 크기를 줄이면(예: uint32로) uint를 사용하는 것이 가장 좋습니다. 일반적으로 다른 방법을 사용하는 데 합당한 이유가 있다고 확신하지 않는 한 uint를 사용합니다.
문자열 유형
Solidity의 문자열 데이터 유형은 재미있는 주제입니다. 누구와 대화하느냐에 따라 다른 의견을 얻을 수 있습니다. Solidity에는 문자열 데이터 유형이 있습니다. 대부분의 사람들이 공유하는 내 의견은 많은 기능을 제공하지 않는다는 것입니다. 문자열 구문 분석, 연결, 바꾸기, 자르기, 문자열 길이 계산까지: 문자열 유형에서 기대할 수 있는 것은 하나도 없으므로 사용자의 책임입니다(필요한 경우). 어떤 사람들은 문자열 대신 bytes32를 사용합니다. 그것도 할 수 있습니다.
Solidity 문자열에 대한 재미있는 기사
내 의견: 자신만의 문자열 유형을 작성하고 일반 사용을 위해 게시하는 것은 재미있는 연습일 수 있습니다.
주소 유형
아마도 Solidity에 고유한 주소 데이터 유형, 특히 이더리움 지갑 또는 계약 주소에 대한 데이터 유형이 있습니다. 특정 크기의 주소를 저장하기 위한 20바이트 값입니다. 또한 해당 유형의 주소에 대한 형식 멤버가 있습니다.
address internal boxingOracleAddr = 0x145ca3e014aaf5dca488057592ee45305d9b3a22;
주소 데이터 유형
날짜/시간 유형
예를 들어 JavaScript에서와 같이 Solidity에는 기본 Date 또는 DateTime 유형이 없습니다. (아, 아니요. 모든 단락에서 Solidity의 소리가 점점 더 나빠집니다!?) 날짜는 기본적으로 uint(uint256) 유형의 타임스탬프로 처리됩니다. 블록 타임스탬프는 Unix 스타일 타임스탬프이므로 일반적으로 밀리초가 아닌 초 단위로 Unix 스타일 타임스탬프로 처리됩니다. 다양한 이유로 사람이 읽을 수 있는 날짜가 필요한 경우 오픈 소스 라이브러리를 사용할 수 있습니다. BoxingOracle: DateLib.sol에서 내가 사용한 것을 알 수 있습니다. OpenZeppelin에는 날짜 유틸리티와 기타 여러 유형의 일반 유틸리티 라이브러리도 있습니다(곧 Solidity의 라이브러리 기능에 대해 설명하겠습니다).
전문가 팁: OpenZeppelin은 지식과 계약을 구축하는 데 도움이 될 수 있는 미리 작성된 일반 코드 모두에 대한 좋은 소스입니다(물론 유일한 좋은 소스는 아닙니다).
매핑
BoxingOracle.sol의 11번째 줄은 mapping 이라는 것을 정의합니다.
mapping(bytes32 => uint) matchIdToIndex;
Solidity의 매핑은 빠른 조회를 위한 특수 데이터 유형입니다. 본질적으로 조회 테이블 또는 해시 테이블과 유사합니다. 여기서 포함된 데이터는 블록체인 자체에 있습니다(매핑이 여기에서와 같이 클래스 구성원으로 정의된 경우). 계약을 실행하는 동안 해시 테이블에 데이터를 추가하는 것과 유사하게 매핑에 데이터를 추가하고 나중에 추가한 값을 조회할 수 있습니다. 이 경우에 우리가 추가하는 데이터는 블록체인 자체에 추가되므로 유지됩니다. 오늘 뉴욕의 매핑에 추가하면 지금부터 일주일 후 이스탄불의 누군가가 읽을 수 있습니다.
BoxingOracle.sol의 71행에서 매핑에 추가하는 예:
matchIdToIndex[id] = newIndex+1
BoxingOracle.sol의 51행에서 매핑 읽기의 예:
uint index = matchIdToIndex[_matchId];
매핑에서 항목을 제거할 수도 있습니다. 이 프로젝트에서는 사용되지 않지만 다음과 같이 보일 것입니다.
delete matchIdToIndex[_matchId];
반환 값
눈치채셨겠지만 Solidity는 Javascript와 표면적으로 약간 비슷할 수 있지만 JavaScript의 느슨한 유형 및 정의를 많이 상속하지 않습니다. 계약 코드는 다소 엄격하고 제한된 방식으로 정의되어야 합니다. 이를 염두에 두고 BoxingOracle.sol의 40행에 있는 함수 정의를 고려하십시오.
function _getMatchIndex(bytes32 _matchId) private view returns (uint) { ... }
자, 우선 여기에 포함된 내용을 간단히 살펴보겠습니다. function
는 그것을 함수로 표시합니다. _getMatchIndex
는 함수 이름입니다(밑줄은 private 멤버를 나타내는 규칙입니다. 나중에 설명하겠습니다). 이것은 bytes32
유형의 _matchId
(이번에는 밑줄 규칙을 사용하여 함수 인수를 표시하는 데 사용됨)라는 이름의 인수 하나를 사용합니다. private
키워드는 실제로 범위 내에서 멤버를 private로 만들고, view
는 이 함수가 블록체인의 데이터를 수정하지 않는다는 것을 컴파일러에 알리고 마지막으로: ~~~ solidity returns (uint) ~~~
이것은 함수가 uint를 반환한다는 것을 의미합니다(void를 반환하는 함수는 여기에 단순히 returns
절이 없을 것입니다). 괄호 안에 uint가 있는 이유는 무엇입니까? Solidity 함수는 튜플 을 반환할 수 있고 종종 수행하기 때문입니다.
이제 166행의 다음 정의를 고려하십시오.
function getMostRecentMatch(bool _pending) public view returns ( bytes32 id, string name, string participants, uint8 participantCount, uint date, MatchOutcome outcome, int8 winner) { ... }
이것에 대한 반환 조항을 확인하십시오! 하나, 둘, 일곱 가지를 반환합니다. 자, 이 함수는 이러한 것들을 튜플로 반환합니다. 왜요? 개발 과정에서 구조체를 반환해야 하는 경우가 종종 있습니다(JavaScript인 경우 JSON 개체를 반환하고 싶을 것입니다). 글쎄, 이 글을 쓰는 시점에서(미래에 변경될 수도 있음) Solidity는 공개 함수에서 구조체 반환을 지원하지 않습니다. 따라서 대신 튜플을 반환해야 합니다. 파이썬 사용자라면 이미 튜플에 익숙할 것입니다. 많은 언어가 적어도 이런 방식으로는 지원하지 않습니다.
튜플을 반환 값으로 반환하는 예는 159행을 참조하세요.
return (_matchId, "", "", 0, 0, MatchOutcome.Pending, -1);
그리고 이와 같은 반환 값을 어떻게 받아들여야 할까요? 우리는 다음과 같이 할 수 있습니다.
var (id, name, part, count, date, outcome, winner) = getMostRecentMatch(false);
또는 올바른 유형을 사용하여 사전에 명시적으로 변수를 선언할 수 있습니다.
//declare the variables bytes32 id; string name; ... etc... int8 winner; //assign their values (id, name, part, count, date, outcome, winner) = getMostRecentMatch(false);
이제 우리는 7개의 반환 값을 보유하기 위해 7개의 변수를 선언했으며 이제 사용할 수 있습니다. 그렇지 않으면 값 중 하나 또는 두 개만 원한다고 가정하면 다음과 같이 말할 수 있습니다.
//declare the variables bytes32 id; uint date; //assign their values (id,,,,date,,) = getMostRecentMatch(false);
우리가 그곳에서 무엇을 했는지 볼까요? 관심이 있는 두 가지만 찾았습니다. 쉼표를 모두 확인하세요. 주의 깊게 계산해야 합니다!
수입품
BoxingOracle.sol의 3행과 4행은 가져오기입니다.
import "./Ownable.sol"; import "./DateLib.sol";
예상대로 BoxingOracle.sol과 동일한 계약 프로젝트 폴더에 있는 코드 파일에서 정의를 가져옵니다.
수정자
함수 정의에는 많은 수정자가 첨부되어 있습니다. 첫째, 가시성이 있습니다: 비공개, 공개, 내부 및 외부 기능 가시성.
또한 pure
및 view
키워드가 표시됩니다. 이것들은 함수가 어떤 종류의 변경을 만들 것인지 컴파일러에 알려줍니다. 이는 기능을 실행하는 최종 가스 비용의 한 요소이기 때문에 중요합니다. 설명은 Solidity Docs를 참조하세요.
마지막으로, 제가 정말로 논의하고 싶은 것은 커스텀 수식어입니다. BoxingOracle.sol의 61행을 살펴보십시오.
function addMatch(string _name, string _participants, uint8 _participantCount, uint _date) onlyOwner public returns (bytes32) {
"public" 키워드 바로 앞에 있는 onlyOwner
수정자를 주목하십시오. 이는 계약 소유자만 이 메서드를 호출할 수 있음을 나타냅니다! 매우 중요하지만 이것은 Solidity의 기본 기능이 아닙니다(미래에 있을 수도 있음). 실제로 onlyOwner
는 우리가 직접 만들고 사용하는 사용자 지정 수정자의 예입니다. 한번 봅시다.
먼저 수정자는 Ownable.sol 파일에 정의되어 있으며 BoxingOracle.sol의 3행에서 가져온 것을 볼 수 있습니다.
import "./Ownable.sol"
수정자를 사용하기 위해 BoxingOracle이 BoxingOracle
에서 상속되도록 Ownable
. 25번째 줄에서 Ownable.sol 내부에서 "Ownable" 계약 내부에서 수정자에 대한 정의를 찾을 수 있습니다.
modifier onlyOwner() { require(msg.sender == owner); _; }
(참고로 이 Ownerable 계약은 OpenZeppelin의 공개 계약 중 하나에서 가져온 것입니다.)
이 것은 수정자로 선언되어 있는 그대로 사용하여 함수를 수정할 수 있음을 나타냅니다. 수정자의 핵심은 "require" 문입니다. Require 문은 어설션과 비슷하지만 디버깅용이 아닙니다. require 문의 조건이 실패하면 함수에서 예외가 발생합니다. 따라서 이 "require" 문을 바꾸어 말하면:
require(msg.sender == owner);
우리는 그것이 의미한다고 말할 수 있습니다:
if (msg.send != owner) throw an exception;
사실, Solidity 0.4.22 이상에서는 해당 require 문에 오류 메시지를 추가할 수 있습니다.

require(msg.sender == owner, "Error: this function is callable by the owner of the contract, only");
마지막으로 호기심 많은 라인에서:
_;
밑줄은 "여기, 수정된 기능의 전체 내용을 실행하십시오."의 약어입니다. 따라서 실제로 require 문이 먼저 실행되고 그 다음에 실제 함수가 실행됩니다. 따라서 수정된 함수에 이 논리 라인을 미리 추가하는 것과 같습니다.
물론 수정자로 할 수 있는 일이 더 많습니다. 문서 확인: 문서.
솔리디티 라이브러리
라이브러리 로 알려진 Solidity의 언어 기능이 있습니다. DateLib.sol의 프로젝트에 예제가 있습니다.
이것은 날짜 유형을 더 쉽게 처리하기 위한 라이브러리입니다. 4행에서 BoxingOracle로 가져옵니다.
import "./DateLib.sol";
그리고 13번째 줄에서 사용됩니다.
using DateLib for DateLib.DateTime;
DateLib.DateTime
은 DateLib 계약에서 내보낸 구조체이며(구성원으로 노출됨, DateLib.sol의 4행 참조) 여기에서 특정 데이터 유형에 대해 DateLib 라이브러리를 "사용"한다고 선언합니다. 따라서 해당 라이브러리에 선언된 메서드와 작업은 우리가 말한 데이터 유형에 적용됩니다. 이것이 Solidity에서 라이브러리가 사용되는 방식입니다.
더 명확한 예를 보려면 SafeMath와 같은 OpenZeppelin의 일부 라이브러리에서 숫자를 확인하십시오. 이것은 기본(숫자) Solidity 데이터 유형에 적용할 수 있으며(여기서 우리는 사용자 정의 데이터 유형에 라이브러리를 적용했습니다) 널리 사용됩니다.
인터페이스
주류 객체 지향 언어에서와 같이 인터페이스가 지원됩니다. Solidity의 인터페이스는 계약으로 정의되지만 함수에 대한 함수 본문은 생략됩니다. 인터페이스 정의의 예는 OracleInterface.sol을 참조하십시오. 이 예에서 인터페이스는 Oracle 계약의 대용으로 사용되며, 그 내용은 별도의 주소가 있는 별도의 계약에 있습니다.
명명 규칙
물론 명명 규칙은 전역 규칙이 아닙니다. 프로그래머로서 우리는 우리에게 호소하는 코딩 및 명명 규칙을 자유롭게 따를 수 있다는 것을 알고 있습니다. 다른 한편으로 우리는 다른 사람들이 우리 코드를 읽고 작업하는 데 편안함을 느끼기를 원하므로 어느 정도 표준화가 바람직합니다.
프로젝트 개요
문제의 코드 파일에 있는 몇 가지 일반적인 언어 기능을 살펴보았으므로 이제 이 프로젝트의 코드 자체를 보다 구체적으로 살펴보겠습니다.
따라서 이 프로젝트의 목적을 다시 한 번 명확히 합시다. 이 프로젝트의 목적은 오라클을 사용하는 스마트 계약의 반현실적(또는 유사 현실적) 데모 및 예를 제공하는 것입니다. 본질적으로 이것은 또 다른 별도의 계약을 부르는 계약일 뿐입니다.
사례의 비즈니스 사례는 다음과 같이 기술될 수 있습니다.
- 사용자는 권투 경기에서 다양한 크기의 내기를 하고, 내기에 돈(이더)을 지불하고, 그들이 이기면 상금을 모으기를 원합니다.
- 사용자는 스마트 계약을 통해 이러한 베팅을 합니다. (실제 사용 사례에서 이것은 web3 프론트엔드가 있는 완전한 DApp일 것입니다. 그러나 우리는 계약 측면만 검토하고 있습니다.)
- 별도의 스마트 계약(오라클)은 제3자가 관리합니다. 그 임무는 현재 상태(보류 중, 진행 중, 완료됨 등) 및 완료되면 승자와 함께 권투 경기 목록을 유지하는 것입니다.
- 주 계약은 오라클에서 보류 중인 일치 목록을 가져와 사용자에게 "베팅 가능한" 일치 항목으로 표시합니다.
- 주 계약은 경기가 시작될 때까지 베팅을 수락합니다.
- 경기가 결정되면 메인 계약은 간단한 알고리즘에 따라 승패를 나누고 자체적으로 컷을 취하고 요청에 따라 상금을 지불합니다(패자는 단순히 전체 지분을 잃음).
베팅 규칙:
- 정의된 최소 베팅이 있습니다(wei로 정의됨).
- 최대 베팅은 없습니다. 사용자는 최소 금액 이상으로 원하는 금액을 베팅할 수 있습니다.
- 사용자는 경기가 "진행 중"이 될 때까지 베팅을 할 수 있습니다.
상금을 나누는 알고리즘:
- 받은 모든 베팅은 "팟"에 배치됩니다.
- 집을 위해 작은 비율이 냄비에서 제거됩니다.
- 각 승자는 베팅의 상대적 크기에 정비례하여 팟의 비율을 받습니다.
- 승리는 매치가 결정된 후 첫 번째 사용자가 결과를 요청하는 즉시 계산됩니다.
- 사용자의 요청에 따라 상금이 수여됩니다.
- 무승부의 경우 아무도 이기지 않습니다. 모두가 자신의 지분을 되찾고 하우스는 컷을 받지 않습니다.
BoxingOracle: Oracle 계약
제공되는 주요 기능
오라클에는 두 가지 인터페이스가 있습니다. 하나는 계약의 "소유자"와 유지 관리자에게 제공되고 다른 하나는 일반 대중에게 제공됩니다. 즉, 오라클을 소비하는 계약입니다. 유지 관리자는 데이터를 계약에 입력하는 기능을 제공하며, 본질적으로 외부 세계에서 데이터를 가져와 블록체인에 넣습니다. 대중에게 해당 데이터에 대한 읽기 전용 액세스를 제공합니다. 계약 자체는 비소유자가 데이터를 편집하는 것을 제한하지만 해당 데이터에 대한 읽기 전용 액세스는 제한 없이 공개적으로 부여된다는 점에 유의하는 것이 중요합니다.
사용자에게:
- 모든 일치 항목 나열
- 보류 중인 일치 항목 나열
- 특정 경기의 세부 정보 얻기
- 특정 경기의 상태 및 결과 가져오기
소유자에게:
- 일치 항목 입력
- 경기 상태 변경
- 경기 결과 설정
사용자 스토리:
- 5월 9일 새로운 권투 시합이 발표되고 확정되었습니다.
- 계약의 유지자인 나(아마도 나는 잘 알려진 스포츠 네트워크이거나 새로운 콘센트일 것입니다)는 블록체인의 오라클 데이터에 다가오는 경기를 "보류 중" 상태로 추가합니다. 이제 누구나 또는 모든 계약에서 원하는 대로 이 데이터를 쿼리하고 사용할 수 있습니다.
- 경기가 시작되면 그 경기의 상태를 "진행 중"으로 설정합니다.
- 경기가 끝나면 경기 상태를 "완료"로 설정하고 경기 데이터를 수정하여 승자를 표시합니다.
Oracle 코드 검토
이 리뷰는 전적으로 BoxingOracle.sol을 기반으로 합니다. 줄 번호는 해당 파일을 참조합니다.
10행과 11행에서 우리는 매치를 위한 저장 장소를 선언합니다:
Match[] matches; mapping(bytes32 => uint) matchIdToIndex;
matches
는 일치 인스턴스를 저장하기 위한 단순한 배열이며 매핑은 배열의 인덱스에 고유한 일치 ID(bytes32 값)를 매핑하기 위한 기능일 뿐입니다. 이 매핑을 사용하여 찾습니다.
17행에서 일치 구조가 정의되고 설명됩니다.
//defines a match along with its outcome struct Match { bytes32 id; //unique id string name; //human-friendly name (eg, Jones vs. Holloway) string participants; //a delimited string of participant names uint8 participantCount; //number of participants (always 2 for boxing matches!) uint date; //GMT timestamp of date of contest MatchOutcome outcome; //the outcome (if decided) int8 winner; //index of the participant who is the winner } //possible match outcomes enum MatchOutcome { Pending, //match has not been fought to decision Underway, //match has started & is underway Draw, //anything other than a clear winner (eg, cancelled) Decided //index of participant who is the winner }
61행: addMatch
함수는 계약 소유자만 사용할 수 있습니다. 저장된 데이터에 새로운 일치 항목을 추가할 수 있습니다.
80행: 함수 declareOutcome
을 사용하면 계약 소유자가 일치를 "결정됨"으로 설정하여 승자가 된 참가자를 설정할 수 있습니다.
102-166행: 다음 함수는 모두 대중이 호출할 수 있습니다. 다음은 일반 대중에게 공개되는 읽기 전용 데이터입니다.
-
getPendingMatches
함수는 현재 상태가 "보류 중"인 모든 일치 항목의 ID 목록을 반환합니다. -
getAllMatches
함수는 모든 일치 항목의 ID 목록을 반환합니다. - 함수
getMatch
는 ID로 지정된 단일 일치 항목의 전체 세부 정보를 반환합니다.
193-204행은 주로 테스트, 디버깅 및 진단을 위한 함수를 선언합니다.
-
testConnection
함수는 계약을 호출할 수 있는지 테스트합니다. - 함수
getAddress
는 이 계약의 주소를 반환합니다. -
addTestData
함수는 일치 목록에 많은 테스트 일치 항목을 추가합니다.
다음 단계로 넘어가기 전에 코드를 조금 둘러보세요. 이 시리즈의 1부에서 설명한 대로 디버그 모드에서 Oracle 계약을 다시 실행하고 다른 함수를 호출하고 결과를 검토하는 것이 좋습니다.
BoxingBets: 클라이언트 계약
클라이언트 계약(베팅 계약)이 책임지는 것과 책임이 없는 것을 정의하는 것이 중요합니다. 클라이언트 계약은 실제 권투 경기 목록을 유지 관리하거나 결과를 선언할 책임이 없습니다 . 우리는 그 서비스에 대한 신탁을 "신뢰"합니다(예, 그 민감한 단어가 있다는 것을 압니다. 어 오, 우리는 3부에서 이에 대해 논의할 것입니다). 고객 계약은 내기를 수락할 책임이 있습니다. 그것은 상금을 나누어 경기 결과(오라클에서 받은 대로)에 따라 승자의 계정으로 이체하는 알고리즘을 담당합니다.
또한 모든 것이 풀 기반이며 이벤트나 푸시가 없습니다. 계약은 오라클에서 데이터를 가져옵니다. 컨트랙트는 (사용자 요청에 대한 응답으로) 오라클에서 경기 결과를 가져오고 컨트랙트가 상금을 계산하고 사용자 요청에 대한 응답으로 이를 전송합니다.
제공되는 주요 기능
- 보류 중인 모든 일치 항목 나열
- 특정 경기의 세부 정보 얻기
- 특정 경기의 상태 및 결과 가져오기
- 내기를 하다
- 상금 요청/받기
클라이언트 코드 검토
이 리뷰는 전적으로 BoxingBets.sol을 기반으로 합니다. 줄 번호는 해당 파일을 참조합니다.
계약의 첫 번째 코드 줄인 12행과 13행은 계약 데이터를 저장할 매핑을 정의합니다.
12행은 사용자 주소를 ID 목록에 매핑합니다. 이것은 사용자에게 속한 베팅의 ID 목록에 사용자를 매핑하는 것입니다. 따라서 주어진 사용자 주소에 대해 해당 사용자가 한 모든 베팅 목록을 빠르게 얻을 수 있습니다.
mapping(address => bytes32[]) private userToBets;
13행은 경기의 고유 ID를 베팅 인스턴스 목록에 매핑합니다. 이를 통해 주어진 경기에 대해 해당 경기에 대해 이루어진 모든 베팅 목록을 얻을 수 있습니다.
mapping(bytes32 => Bet[]) private matchToBets;
17행과 18행은 오라클에 대한 연결과 관련이 있습니다. 먼저 boxingOracleAddr
변수에 Oracle 계약의 주소를 저장합니다(기본적으로 0으로 설정됨). 우리는 오라클의 주소를 하드코딩할 수는 있지만 절대 변경할 수 없습니다. (오라클의 주소를 변경할 수 없다는 것은 좋은 것일 수도 있고 나쁜 것일 수도 있습니다. 이에 대해서는 3부에서 논의할 수 있습니다.) 다음 줄은 Oracle 인터페이스(OracleInterface.sol에 정의됨)의 인스턴스를 생성하고 변수에 저장합니다.
//boxing results oracle address internal boxingOracleAddr = 0; OracleInterface internal boxingOracle = OracleInterface(boxingOracleAddr);
58행으로 이동하면 이 오라클 주소를 변경할 수 있고 boxingOracle
인스턴스가 새 주소로 다시 인스턴스화되는 setOracleAddress
함수를 볼 수 있습니다.
21행은 최소 베팅 크기를 wei로 정의합니다. 이것은 물론 실제로는 0.000001 에테르에 불과한 아주 작은 양입니다.
uint internal minimumBet = 1000000000000;
58행과 66행에는 각각 setOracleAddress
및 getOracleAddress
함수가 있습니다. setOracleAddress
에는 onlyOwner
수정자가 있습니다. 그 이유는 계약 소유자만이 오라클을 다른 오라클로 전환할 수 있기 때문입니다(좋은 생각은 아니지만 3부에서 자세히 설명하겠습니다). 반면에 getOracleAddress
함수는 공개적으로 호출할 수 있습니다. 누구든지 오라클이 사용되는 것을 볼 수 있습니다.
function setOracleAddress(address _oracleAddress) external onlyOwner returns (bool) {... function getOracleAddress() external view returns (address) { ....
72행과 79행에는 각각 getBettableMatches
및 getMatch
함수가 있습니다. 이것은 단순히 호출을 오라클에 전달하고 결과를 반환한다는 점에 유의하십시오.
function getBettableMatches() public view returns (bytes32[]) {... function getMatch(bytes32 _matchId) public view returns ( ....
placeBet
기능은 매우 중요한 기능입니다(108행).
function placeBet(bytes32 _matchId, uint8 _chosenWinner) public payable { ...
이것의 눈에 띄는 기능은 payable
수정자입니다. 우리는 일반적인 언어 기능에 대해 논의하느라 너무 바빠서 함수 호출과 함께 돈을 보낼 수 있는 핵심적인 중요한 기능에 대해서는 아직 다루지 않았습니다! 그것이 기본적으로 그 자체입니다. 전송된 다른 인수 및 데이터와 함께 금액을 받아들일 수 있는 함수입니다.
여기에서 사용자가 자신이 할 내기, 해당 내기에서 얼마나 많은 돈을 벌고자 하는지, 실제로 돈을 보낼 것인지를 동시에 정의하기 때문에 이것이 필요합니다. payable
수정자는 이를 가능하게 합니다. 내기를 수락하기 전에 우리는 내기가 유효한지 확인하기 위해 여러 가지 검사를 합니다. 111행의 첫 번째 검사는 다음과 같습니다.
require(msg.value >= minimumBet, "Bet amount must be >= minimum bet");
보낸 금액은 msg.value
에 저장됩니다. 모든 수표가 123행에서 통과한다고 가정하면 해당 금액을 오라클의 소유권으로 이전하고 해당 금액의 소유권을 사용자로부터 계약의 소유로 가져갑니다.
address(this).transfer(msg.value);
마지막으로 136행에는 계약이 유효한 오라클에 연결되어 있는지 여부를 알려주는 테스트/디버깅 도우미 기능이 있습니다.
function testOracleConnection() public view returns (bool) { return boxingOracle.testConnection(); }
마무리
그리고 이것은 실제로 이 예가 진행되는 한입니다. 그냥 내기를 수락합니다. 상금을 나누고 지불하는 기능과 다른 논리는 우리의 목적에 맞게 간단하게 예제를 유지하기 위해 의도적으로 생략되었습니다. 이는 단순히 계약과 함께 오라클의 사용을 보여주기 위한 것입니다. 더 완전하고 복잡한 로직은 현재 이 예제의 확장이며 아직 개발 중인 다른 프로젝트에 있습니다.
이제 우리는 코드베이스에 대해 더 잘 이해하게 되었고 이를 Solidity에서 제공하는 일부 언어 기능에 대해 논의하기 위한 수단이자 출발점으로 사용했습니다. 이 3부작 시리즈의 주요 목적은 오라클과의 계약 사용을 시연하고 논의하는 것입니다. 이 부분의 목적은 이 특정 코드를 조금 더 잘 이해하고 이를 솔리디티 및 스마트 계약 개발의 일부 기능을 이해하는 시작점으로 사용하는 것입니다. 세 번째이자 마지막 부분의 목적은 오라클 사용의 전략과 철학, 그리고 그것이 개념적으로 스마트 계약 모델에 어떻게 맞는지 논의하는 것입니다.
추가 선택적 단계
나는 더 많은 것을 배우고자 하는 독자들이 이 코드를 가지고 놀기를 적극 권장합니다. 새로운 기능을 구현합니다. 모든 버그를 수정합니다. 구현되지 않은 기능(예: 결제 인터페이스)을 구현합니다. 함수 호출을 테스트합니다. 수정하고 어떤 일이 일어나는지 다시 테스트하십시오. web3 프런트 엔드를 추가합니다. 일치 항목을 제거하거나 결과를 수정하는 기능을 추가합니다(실수인 경우). 취소된 경기는 어떻게 되나요? 두 번째 오라클을 구현합니다. 물론 계약은 얼마든지 오라클을 자유롭게 사용할 수 있지만, 그게 무슨 문제가 될까요? 그것을 즐기십시오. 그것은 배울 수 있는 좋은 방법이며, 그렇게 할 때(그리고 즐거움을 얻을 때) 배운 내용을 더 많이 유지하게 될 것입니다.
시도할 수 있는 비포괄적인 샘플 목록:
- 로컬 테스트넷에서 계약과 오라클을 모두 실행하고(1부에서 설명한 대로 트러플에서) 모든 호출 가능한 함수와 모든 테스트 함수를 호출합니다.
- 경기 종료 시 상금을 계산하고 지급하는 기능을 추가합니다.
- 무승부 시 모든 베팅을 환불하는 기능을 추가합니다.
- 경기가 시작되기 전에 환불 또는 취소를 요청하는 기능을 추가하세요.
- 때때로 경기가 취소될 수 있다는 사실을 허용하는 기능을 추가하십시오(이 경우 모든 사람이 환불을 받아야 함).
- 사용자가 내기를 걸 때 있었던 오라클이 해당 경기의 결과를 결정하는 데 사용되는 것과 동일한 오라클임을 보장하는 기능을 구현하십시오.
- 관련된 몇 가지 다른 기능이 있는 또 다른(두 번째) 오라클을 구현하거나 권투 이외의 스포츠를 제공할 수 있습니다(참가자 수 및 목록은 다양한 유형의 스포츠를 허용하므로 실제로 권투에만 국한되지 않음). .
-
getMostRecentMatch
를 구현하여 가장 최근에 추가된 일치 항목 또는 발생 시점 측면에서 현재 날짜와 가장 가까운 일치 항목을 실제로 반환하도록 합니다. - 예외 처리를 구현합니다.
계약과 오라클 간의 관계 메커니즘에 익숙해지면 이 3부작 시리즈의 3부에서 이 예제에서 제기한 전략적, 디자인 및 철학적 문제에 대해 논의할 것입니다.