스마트 컨트랙트 개발언어는 솔리디티만 있는 게 아닙니다.
바이러라는 최근 급성장하는 언어가 있습니다. 이것의 문법은 파이썬과 매우 유사합니다. 이름이 왜 바이퍼인가 했더니 파이썬과 문법이 유사해서 그렇습니다. 파이썬도 뱀을 의미하고, 바이퍼도 뱀을 의미하거든요. 좀 더 찾아보니 바이퍼는 파이썬3를 기반으로 만들었고, 바이퍼의 문법은 파이썬3과 호환된다고 합니다.
솔리디티와 바이퍼의 깃헙 통계를 비교해 보겠습니다.
2019년 8월 20일 기준
솔리디티
- watch: 489
- star: 7, 219
- fork: 1,971
- contributor: 318
바이퍼 - watch: 164
- star: 2,442
- fork: 398
- contributor: 119
통계로 보면, 아직도 솔리디티가 대세인 것으로 보입니다.
솔리디티와 바이퍼는 문법은 다르지만 컴파일 결과 얻어지는 결과물은 ABI와 바이트코드로 동일합니다.물론 코드에 따라 바이트코드가 생성되는 것이기 때문에, 바이트코드 자체가 동일하지는 않습니다. 그러나 EVM에서는 바이트코드가 솔리디티 코드로 컴파일된건지, 바이버 코드로 컴파일된건지 따지지 않습니다.
솔리디티 코드와 바이퍼 코드 비교
바이퍼 코드 샘플입니다. 정말로 파이썬 코드를 보는 것 같습니다. 조금 다른 점이라면 변수를 선언하는 점이 좀 다르네요.
# Open Auction
# Auction params
# Beneficiary receives money from the highest bidder
beneficiary: public(address)
auctionStart: public(timestamp)
auctionEnd: public(timestamp)
# Current state of auction
highestBidder: public(address)
highestBid: public(wei_value)
# Set to true at the end, disallows any change
ended: public(bool)
# Keep track of refunded bids so we can follow the withdraw pattern
pendingReturns: public(map(address, wei_value))
# Create a simple auction with `_bidding_time`
# seconds bidding time on behalf of the
# beneficiary address `_beneficiary`.
@public
def __init__(_beneficiary: address, _bidding_time: timedelta):
self.beneficiary = _beneficiary
self.auctionStart = block.timestamp
self.auctionEnd = self.auctionStart + _bidding_time
# Bid on the auction with the value sent
# together with this transaction.
# The value will only be refunded if the
# auction is not won.
@public
@payable
def bid():
# Check if bidding period is over.
assert block.timestamp < self.auctionEnd
# Check if bid is high enough
assert msg.value > self.highestBid
# Track the refund for the previous high bidder
self.pendingReturns[self.highestBidder] += self.highestBid
# Track new high bid
self.highestBidder = msg.sender
self.highestBid = msg.value
# Withdraw a previously refunded bid. The withdraw pattern is
# used here to avoid a security issue. If refunds were directly
# sent as part of bid(), a malicious bidding contract could block
# those refunds and thus block new higher bids from coming in.
@public
def withdraw():
pending_amount: wei_value = self.pendingReturns[msg.sender]
self.pendingReturns[msg.sender] = 0
send(msg.sender, pending_amount)
# End the auction and send the highest bid
# to the beneficiary.
@public
def endAuction():
# It is a good guideline to structure functions that interact
# with other contracts (i.e. they call functions or send Ether)
# into three phases:
# 1. checking conditions
# 2. performing actions (potentially changing conditions)
# 3. interacting with other contracts
# If these phases are mixed up, the other contract could call
# back into the current contract and modify the state or cause
# effects (Ether payout) to be performed multiple times.
# If functions called internally include interaction with external
# contracts, they also have to be considered interaction with
# external contracts.
# 1. Conditions
# Check if auction endtime has been reached
assert block.timestamp >= self.auctionEnd
# Check if this function has already been called
assert not self.ended
# 2. Effects
self.ended = True
# 3. Interaction
send(self.beneficiary, self.highestBid)
솔리디티를 아직 접하지 못한 개발자라면, 솔리디티보다 바이퍼로 시작하는 것도 나쁘지 않겠습니다. 사실 솔리디티가 유명하지만, 보안 문제가 좀 있거든요
솔리디티 및 기타 언어로 배포된 스마트 컨트랙트의 주요 보안 문제는 다음과 같습니다.
- 코드 결함으로 누구나 삭제할 수 있는 컨트랙트
- 이더를 아무 주소나 보낼 수 있게 코딩된 컨트랙트
- 이더를 빼가지 못하는 상태에 도달하는 컨트랙트
이외에더 여러 경우가 있습니다. 그 이유는 솔리디티가 코더에게 거의 무한한 자유를 주기 때문입니다. 막말로, 컨트랙트의 selfdestruct함수에서 발행한 사람을 체크하는 코드가 없다면 누구나 컨트랙트를 삭제할 수도 있는 것입니다.
바이퍼는 솔리디티의 자유도는 낮춤으로써 보안성을 확보했습니다.
- modifier를 없앴습니다.
- 변경자에서 다른 함수를 호출할 수가 있는데, 그 함수에서 뭔가를 하는지 파악하기 어려울 수 있기 때문입니다.
- 저도 코딩하면서 굳이 modifier가 필요한가 싶었습니다. 그냥 assert나, require함수를 쓰면 되거든요.
- 클래스 상속을 없앴습니다.
- 클래스를 상속해서 새로운 컨트랙트를 만들면, 원 클래스의 소스 분석이 필요해서 코드 해석이 어렵기 때문입니다.
- 인라인 어셈블리를 지원하지 않습니다.
- C나 C++에서도 asm키워드로 지원하는 인라인 어셈블리가 솔리디티에서도 가능합니다. 상위 레벨 언어가 아니라 load나 move와 같은 저수준 언어로 코딩하는 것은 가독성을 크게 떨어뜨리기 때문입니다.
- 함수 오버로딩을 지원하지 않습니다.
- 코드 해석을 어렵게 만들기 때문입니다.
- 물론, 연산자 오버로딩도 지원하지 않습니다.
- 재귀함수를 지원하지 않습니다. 또한 무한루프를 지원하지 않습니다.
- 컨트랙트 동작이 무한 루프에 빠지는 것을 막기 위해서입니다.
- 만약 무한 루프가 있다면 컨트랙트를 실행하기 위한 개스소비도 무한대로 늘어날 것입니다.
- 명시적 변수 형변환을 위해 별도의 함수(convert)를 제공합니다.
- 일반적인 언어에서는 명시적 형변환의 경우 데이터 소실이 발생하는데, 바이퍼는 convert함수를 제공하여 손실이 없도록 합니다.
- 또한, 명시적 형변환을 사용자가 책임을 가지고 하도록 분명히 합니다.
&. 바이너리 소수점을 지원하지 않습니다. - 0.001100110011과 같이 바이너리 소수점 형태로 표시하면, 근사화가 필요하기 때문입니다.
- 함수 순서가 매우 중요합니다.
- C언어와 마찬가지로, 먼저 선언되거나 정의되지 않은 함수를 호출하면 컴파일 에러가 발생합니다.
- 솔리디티는 C++이나 Java와 같이 먼저 선언되지 않더라도 함수 호출이 가능합니다.
바이퍼의 컨트랙트는 다음과 같은 구조를 가집니다.
파이썬 문법을 따르다보니 파이썬에서 많이 보는 @public과 같은 @기호를 이용한 데코레이터를 지원합니다. 또한 파이썬과 같이 함수에서 self라는 키워드를 사용하여 컨트랙트 인스턴스에 접근할 수 있습니다. 즉 컨트랙트에 속한 어떤 함수에서라도 인스턴스 변수에 접근이 가능합니다.
또한 바이퍼는 솔리티티에는 없는 몇가지 데이터 타입을 제공합니다.
- timestamp
- timedelta
- wei_value
- seconds
- wei
- wei per second
컨트랙트에서 변수 오버플로우 문제는 치명적입니다. 비트코인의 경우 2010년에 데이터 타입이 signed인 점을 이용해서 오버플로우를 의도적으로 이용해서 엄청난 수의 비트코인을 발행한 사건이 있었습니다. 이더리움의 경우도 2018년 4월에 이와 유사한 사례가 발생했습니다. 바이퍼는 오버플로우 검출기능을 내장하고 있습니다.
- SafeMath에 준하는 기능
- Literal(문자열, 상수등 고정값)에 대해서 클램프를 사용하여 범위를 제한. 오버플로우 방지.
솔리디티 컴파일러는 C++로 작성되어 있는데, 바이퍼는 파이썬으로 작성되어 있습니다.
바이퍼도 온라인에서 컴파일할 수 있습니다.
https://vyper.online
배포는 아직 안되나 봅니다. 하지만 바이퍼 코드를 컴파일해서 얻어진 바이트코드와 ABI를 이용하여 리믹스에서 배포도 가능합니다. 아 새로운 리믹스는 바이퍼를 정식 지원하고 있습니다!
Truffle이라는 스마트 컨트랙트 개발 플랫폼이 있습니다. 바이퍼도 지원됩니다.
https://truffleframework.com/blog/truffle-v5-has-arrived
Truffle을 사용하면 테스트넷 셋업등이 매우 편합니다.
결론적으로 스마트 컨트랙트 개발을 솔리디티가 아니라 바이퍼로 해도 큰 이슈는 없는 걸로 보입니다. 그러나 많은 사람들이 바이퍼가 솔리디티를 대체하는 것은 아니라고 합니다. 그런데 바이퍼가 더 보안이 강하고, 솔리디티와 유사한 성능을 낸다면 많은 사람들이 바이퍼를 사용할 거 같습니다.