Smart contract security
audit report
Audit Number:202009141210
Smart Contract Name:
wing.py
Smart Contract Hash:
00c59fcd27a562d6397883eab1f2fff56e58ef80
Smart Contract Address Link:
https:#github.com/wing-groups/wing-dao-contracts/blob/master/neovm/wing.py
Commit Hash:
13339115d3d85cc6a2d62242fd4ec8377a1e373e
Start Date:2020.09.12
Completion Date:2020.09.13
Overall Result:Pass
Audit Team: Beosin (Chengdu LianAn) Technology Co. Ltd.
Audit Categories and Results:
No. Categories Subitems Results
OEP4 Token Standards Pass
Operator Security Audit Pass
Dictionary Use Security Audit Pass
Redundant Code Pass
Slice Security Audit Pass
1 Coding Conventions Module Audit Pass
Subscript Type Audit Pass
The Assert Audit Pass
Overriding Variables Pass
Gas Consumption Pass
Division By Zero Audit Pass
2 Function Call Audit Authorization of Function Call Pass
Returned Value Security Pass
RegisterAppCall & DynamicAppCall
Pass
Security Audit
Integer Overflow/Underflow Pass
Reentrancy Pass
Transaction-Ordering Dependence Pass
Pseudo-random Number Generator
Pass
(PRNG)
3 Common Vulnerability Audit Array subscript out-of-bounds audit Pass
DoS (Denial of Service) Pass
Token Vesting Implementation Missing
Data Injection Attack Audit Pass
event security Pass
Super Account Authority Audit Pass
4 Business Security Business Logics Pass
Business Implementations Pass
Note: Audit results and suggestions in code comments
Disclaimer: This audit is only applied to the type of auditing specified in this report and the scope of given in
the results table. Other unknown security vulnerabilities are beyond auditing responsibility. Beosin (Chengdu
LianAn) Technology only issues this report based on the attacks or vulnerabilities that already existed or
occurred before the issuance of this report. For the emergence of new attacks or vulnerabilities that exist or
occur in the future, Beosin (Chengdu LianAn) Technology lacks the capability to judge its possible impact on
the security status of smart contracts, thus taking no responsibility for them. The security audit analysis and
other contents of this report are based solely on the documents and materials that the contract provider has
provided to Beosin (Chengdu LianAn) Technology before the issuance of this report, and the contract
provider warrants that there are no missing, tampered, deleted; if the documents and materials provided by the
contract provider are missing, tampered, deleted, concealed or reflected in a situation that is inconsistent with
the actual situation, or if the documents and materials provided are changed after the issuance of this report,
Beosin (Chengdu LianAn) Technology assumes no responsibility for the resulting loss or adverse effects. The
audit report issued by Beosin (Chengdu LianAn) Technology is based on the documents and materials
provided by the contract provider, and relies on the technology currently possessed by Beosin (Chengdu
LianAn). Due to the technical limitations of any organization, this report conducted by Beosin (Chengdu
LianAn) still has the possibility that the entire risk cannot be completely detected. Beosin (Chengdu LianAn)
disclaims any liability for the resulting losses.
The final interpretation of this statement belongs to Beosin (Chengdu LianAn).
Audit Results Explained:
Beosin (Chengdu LianAn) Technology has used several methods including Formal Verification, Static
Analysis, Typical Case Testing and Manual Review to audit three major aspects of smart contracts WING,
including Coding Standards, Security, and Business Logic. WING contracts passed all audit items. The
overall result is Pass (Distinction). The smart contract is able to function properly. Please find below the
basic information of the smart contract:
1、Basic Token Information
Token name WingToken
Token symbol WING
decimals 9
totalSupply 2005184.72 (Mintable with the cap of 10 billion)
Token type OEP4
Table 1 - Basic Token Information
2、Token Vesting Information
Missing
3、Custom Function Description
Initialization
The contract mints 2 million tokens to the address AUKZ3KL1FRRhgcijH6DBdBtswUdtmqL8Wo during the
initialization.
The unboundTokenToPool function
The contract implements the unboundTokenToPool function, which is used to unbind WING to the product
pool (Governance, currently 38f957755afd12b2149a3490af00a13385f2073e), that is, to mint tokens to the
product pool address. The minting is divided into 14 epochs, the rewards and duration of each epoch are
different, and the total duration is about 9 years and 50 days. A total of 8 million tokens were minted.
Figure 1 source code screenshot (1/2)
Figure 2 source code screenshot (2/2)
Audited Source Code with Comments:
from ontology.interop.Ontology.Contract import Migrate
from ontology.interop.System.ExecutionEngine import GetCallingScriptHash
OntCversion = '2.0.0'
from ontology.builtins import concat
from ontology.interop.Ontology.Runtime import Base58ToAddress
from ontology.interop.System.Action import RegisterAction
from ontology.interop.System.Runtime import Notify, CheckWitness, GetTime
from ontology.interop.System.Storage import GetContext, Get, Put, Delete
TransferEvent = RegisterAction("transfer", "from", "to", "amount") # Beosin (Chengdu LianAn) # Declare the e
vent 'TransferEvent'.
ApprovalEvent = RegisterAction("approval", "owner", "spender", "amount") # Beosin (Chengdu LianAn) # Decl
are the event 'ApprovalEvent'.
ctx = GetContext()
NAME = 'WingToken' # Beosin (Chengdu LianAn) # The token name is 'WingToken'.
SYMBOL = 'WING' # Beosin (Chengdu LianAn) # The token symbol is 'WING'.
DECIMALS = 9 # Beosin (Chengdu LianAn) # The token dedimals is 9.
FACTOR = 1000000000
COMMUNITY_FUND = Base58ToAddress("AUKZ3KL1FRRhgcijH6DBdBtswUdtmqL8Wo")
OPERATOR = 'Operator'
PENDING_OPERATOR = 'PendingOperator'
BALANCE_PREFIX = bytearray(b'\x01')
APPROVE_PREFIX = b'\x02'
SUPPLY_KEY = 'TotalSupply'
GOVERNANCE_ADDRESS = 'Governance'
COMMUNITY_BONUS_COUNT = 'Community'
UNBOUNT_STATUS = 'Status'
START = 'StartTime'
START_OFFSET = 'StartOffset'
YEAR = 31536000
MONTH = 2678400
DAY = 86400
UNBOUND_GENERATION_AMOUNT = [6, 60, 30, 18, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1]
UNBOUND_GENERATION_DURITION = [3 * DAY, 5 * DAY, 5 * DAY, 5 * DAY, YEAR -
18 * DAY, YEAR, YEAR, YEAR, YEAR, YEAR, YEAR,
YEAR, YEAR, 4256000]
# curve's factor
DAY_FACTOR = 100
COMMUNITY_BONUS = 500000 * FACTOR * 4
INIT = 'init'
def Main(operation, args):
"""
:param operation:
:param args:
:return:
"""
# 'init' has to be invokded first after deploying the contract to store the necessary and important info in the blockc
hain
if operation == 'init':
assert (len(args) == 2)
governanceAddress = args[0]
start = args[1]
return init(governanceAddress, start)
if operation == 'name':
return name()
if operation == 'symbol':
return symbol()
if operation == 'decimals':
return decimals()
if operation == 'totalSupply':
return totalSupply()
if operation == 'balanceOf':
assert (len(args) == 1)
acct = args[0]
return balanceOf(acct)
if operation == 'transfer':
assert (len(args) == 3)
from_acct = args[0]
to_acct = args[1]
amount = args[2]
return transfer(from_acct, to_acct, amount)
if operation == 'transferMulti':
return transferMulti(args)
if operation == 'transferFrom':
assert (len(args) == 4)
spender = args[0]
from_acct = args[1]
to_acct = args[2]
amount = args[3]
return transferFrom(spender, from_acct, to_acct, amount)
if operation == 'approve':
assert (len(args) == 3)
owner = args[0]
spender = args[1]
amount = args[2]
return approve(owner, spender, amount)
if operation == 'allowance':
assert (len(args) == 2)
owner = args[0]
spender = args[1]
return allowance(owner, spender)
if operation == 'unboundTokenToPool':
return unboundTokenToPool()
if operation == 'setGovernanceAddress':
assert (len(args) == 1)
setGovernanceAddress = args[0]
return setGovernanceAddress(setGovernanceAddress)
if operation == 'getGovernanceAddress':
return getGovernanceAddress()
if operation == 'disableUnboundToken':
return disableUnboundToken()
if operation == 'enableUnboundToken':
return enableUnboundToken()
if operation == 'migrateContract':
assert (len(args) == 7)
code = args[0]
needStorage = args[1]
name = args[2]
version = args[3]
author = args[4]
email = args[5]
description = args[6]
return migrateContract(code, needStorage, name, version, author, email, description)
if operation == 'setPendingOperator':
assert (len(args) == 1)
operator = args[0]
return setPendingOperator(operator)
if operation == 'getPendingOperator':
return getPendingOperator()
if operation == 'confirmPendingOperator':
return confirmPendingOperator()
return False
# Beosin (Chengdu LianAn) # Initialize the contract.
def init(governanceAddress, start):
"""
initialize the contract, put some important info into the storage in the blockchain
:return:
"""
operator = Base58ToAddress("AdYLQGmVWdDtp195eio5yzW8HhDH8h4Q9t")
assert (start >= GetTime())
assert (CheckWitness(operator)) # Beosin (Chengdu LianAn) # Check the operator permission.
assert (len(COMMUNITY_FUND) == 20)
assert (len(governanceAddress) == 20)
assert (not Get(ctx, INIT)) # Beosin (Chengdu LianAn) # Require that the contract can only be initialized o
nce.
Put(ctx, GOVERNANCE_ADDRESS, governanceAddress)
Put(ctx, OPERATOR, operator)
Put(ctx, COMMUNITY_BONUS_COUNT, 0)
Put(ctx, START, start)
Put(ctx, START_OFFSET, 0)
Put(ctx, INIT, 1)
mint(COMMUNITY_FUND, COMMUNITY_BONUS) # Beosin (Chengdu LianAn) # Mint total of 2 millio
n tokens to the COMMUNITY_FUND address.
return True
def name():
"""
:return: name of the token
"""
return NAME
def symbol():
"""
:return: symbol of the token
"""
return SYMBOL
def decimals():
"""
:return: the decimals of the token
"""
return DECIMALS + 0
def totalSupply():
"""
:return: the total supply of the token
"""
return Get(ctx, SUPPLY_KEY) + 0
def balanceOf(account):
"""
:param account:
:return: the token balance of account
"""
if len(account) != 20:
raise Exception("address length error")
return Get(ctx, concat(BALANCE_PREFIX, account)) + 0
# Beosin (Chengdu LianAn) # The function 'transfer' is defined to transfer tokens.
def transfer(from_acct, to_acct, amount):
"""
Transfer amount of tokens from from_acct to to_acct
:param from_acct: the account from which the amount of tokens will be transferred
:param to_acct: the account to which the amount of tokens will be transferred
:param amount: the amount of the tokens to be transferred, >= 0
:return: True means success, False or raising exception means failure.
"""
if len(to_acct) != 20 or len(from_acct) != 20:
raise Exception("address length error")
if CheckWitness(from_acct) == False or amount < 0: # Beosin (Chengdu LianAn) # Require that the address
'from_acct' should has corresponding permission and the transfer amount should not be less than 0.
return False # Beosin (Chengdu LianAn) # Throwing exception when transfer faild is recommended.
# Beosin (Chengdu LianAn) # Update the token balance of 'from_acct'.
fromKey = concat(BALANCE_PREFIX, from_acct)
fromBalance = Get(ctx, fromKey)
if amount > fromBalance:
return False # Beosin (Chengdu LianAn) # Throwing exception when transfer faild is recommended.
if amount == fromBalance:
Delete(ctx, fromKey)
else:
Put(ctx, fromKey, fromBalance - amount)
# Beosin (Chengdu LianAn) # Update the token balance of 'to_acct'.
toKey = concat(BALANCE_PREFIX, to_acct)
toBalance = Get(ctx, toKey)
Put(ctx, toKey, toBalance + amount)
TransferEvent(from_acct, to_acct, amount) # Beosin (Chengdu LianAn) # Trigger the event 'TransferEvent'
.
return True
# Beosin (Chengdu LianAn) # The function 'transferMulti' is defined to multiply transfer tokens.
def transferMulti(args):
"""
:param args: the parameter is an array, containing element like [from, to, amount]
:return: True means success, False or raising exception means failure.
"""
for p in args:
if len(p) != 3:
# return False is wrong
raise Exception("transferMulti params error.")
if transfer(p[0], p[1], p[2]) == False: # Beosin (Chengdu LianAn) # Call the transfer function to transfer t
okens and check its return value.
# return False is wrong since the previous transaction will be successful
raise Exception("transferMulti failed.")
return True
# Beosin (Chengdu LianAn) # The function 'approve' is defined to allow specified amount of tokens to a spec
ified address.
def approve(owner, spender, amount):
"""
owner allow spender to spend amount of token from owner account
Note here, the amount should be less than the balance of owner right now.
:param owner:
:param spender:
:param amount: amount>=0
:return: True means success, False or raising exception means failure.
"""
if len(spender) != 20 or len(owner) != 20:
raise Exception("address length error")
if CheckWitness(owner) == False: # Beosin (Chengdu LianAn) # Require that the address 'owner' should h
as corresponding permission.
return False
if amount > balanceOf(owner) or amount < 0: # Beosin (Chengdu LianAn) # Require that the allowed amou
nt should not exceed the holder’s balance and the allowed amount should not be less than 0.
return False
# Beosin (Chengdu LianAn) # Set the approval amount between 2 addresses.
key = concat(concat(APPROVE_PREFIX, owner), spender)
Put(ctx, key, amount)
ApprovalEvent(owner, spender, amount) # Beosin (Chengdu LianAn) # Trigger the event 'ApprovalEvent'.
return True
# Beosin (Chengdu LianAn) # The function 'transferFrom' is defined to delegate transfer tokens from addre
ss 'from_acct'.
def transferFrom(spender, from_acct, to_acct, amount):
"""
spender spends amount of tokens on the behalf of from_acct, spender makes a transaction of amount of tokens
from from_acct to to_acct
:param spender:
:param from_acct:
:param to_acct:
:param amount:
:return:
"""
if len(spender) != 20 or len(from_acct) != 20 or len(to_acct) != 20:
raise Exception("address length error")
if CheckWitness(spender) == False: # Beosin (Chengdu LianAn) # Require that the address 'spender' shoul
d has corresponding permission.
return False # Beosin (Chengdu LianAn) # Throwing exception when transfer faild is recommended.
fromKey = concat(BALANCE_PREFIX, from_acct)
fromBalance = Get(ctx, fromKey)
if amount > fromBalance or amount < 0: # Beosin (Chengdu LianAn) # Require the transfer amount should
not exceed the holder's balance and the transfer amount should not be less than 0.
return False # Beosin (Chengdu LianAn) # Throwing exception when transfer faild is recommended.
# Beosin (Chengdu LianAn) # Query the approved amount between addresses from_acct and spender.
approveKey = concat(concat(APPROVE_PREFIX, from_acct), spender)
approvedAmount = Get(ctx, approveKey)
toKey = concat(BALANCE_PREFIX, to_acct)
if amount > approvedAmount:
return False # Beosin (Chengdu LianAn) # Throwing exception when transfer faild is recommended.
# Beosin (Chengdu LianAn) # Update the approved amount and the token balance of holder.
elif amount == approvedAmount:
Delete(ctx, approveKey)
Put(ctx, fromKey, fromBalance - amount)
else:
Put(ctx, approveKey, approvedAmount - amount)
Put(ctx, fromKey, fromBalance - amount)
# Beosin (Chengdu LianAn) # Update the token balance of the 'to_acct'.
toBalance = Get(ctx, toKey)
Put(ctx, toKey, toBalance + amount)
TransferEvent(from_acct, to_acct, amount) # Beosin (Chengdu LianAn) # Trigger the event 'TransferEvent'
.
return True
def allowance(owner, spender):
"""
check how many token the spender is allowed to spend from owner account
:param owner: token owner
:param spender: token spender
:return: the allowed amount of tokens
"""
key = concat(concat(APPROVE_PREFIX, owner), spender)
return Get(ctx, key) + 0
# 解绑 wing 到产品池
def unboundTokenToPool():
assert (Get(ctx, INIT)) # Beosin (Chengdu LianAn) # Require that the contract initialization should be com
pleted.
caller = GetCallingScriptHash() # Beosin (Chengdu LianAn) # Get the script hash of the caller.
gov = getGovernanceAddress()
assert (gov == caller) # Beosin (Chengdu LianAn) # Require that only the Governance address can call this
function.
# 给治理产品池分钱(规则:每年递减,递减规则参见白皮书)
assert (not Get(ctx, UNBOUNT_STATUS))
length = len(UNBOUND_GENERATION_AMOUNT)
epoch = [0]
# Beosin (Chengdu LianAn) # Record the current time offset of each epoch.
for i in range(1, length + 1):
epoch.append(epoch[i - 1] + UNBOUND_GENERATION_DURITION[i - 1])
startOffset = Get(ctx, START_OFFSET)
startTime = Get(ctx, START)
if startOffset >= epoch[length]:
return 0
endOffset = GetTime() -
startTime # Beosin (Chengdu LianAn) # Get the difference between the current time and the start time.
# Beosin (Chengdu LianAn) # If the difference between the current time and the start time is greater than
the maximum offset of the epoch, set the difference to the maximum offset of the epoch.
if endOffset > epoch[length]:
endOffset = epoch[length]
startIdx = 0
endIdx = 0
for i in range(len(epoch)):
if startOffset >= epoch[i]:
startIdx = i # Beosin (Chengdu LianAn) # Get the index of the previous epoch of startOffset.
if endOffset >= epoch[i]:
endIdx = i # Beosin (Chengdu LianAn) # Get the index of the previous epoch of endOffset.
amount = 0
if startIdx == endIdx: # Beosin (Chengdu LianAn) # If at the same epoch.
amount = (endOffset -
startOffset) * UNBOUND_GENERATION_AMOUNT[startIdx] # Beosin (Chengdu LianAn) # The 'amount'
is the product of time difference and epoch reward.
else: # Beosin (Chengdu LianAn) # If at the different epoch.
amount += (epoch[startIdx + 1] -
startOffset) * UNBOUND_GENERATION_AMOUNT[startIdx] # Beosin (Chengdu LianAn) # Calculate the a
mount from startOffset to the next adjacent epoch.
if endIdx < length:
amount += (endOffset -
epoch[endIdx]) * UNBOUND_GENERATION_AMOUNT[endIdx] # Beosin (Chengdu LianAn) # Calculate th
e reward from the previous adjacent epoch to endOffset.
for i in range(endIdx - 1, startIdx, -1):
amount += UNBOUND_GENERATION_DURITION[i] * UNBOUND_GENERATION_AMOUNT[i] #
Beosin (Chengdu LianAn) # Calculate the rewards corresponding to the entire epoch.
amount = amount * FACTOR / DAY_FACTOR # Beosin (Chengdu LianAn) # Calculate the number of toke
ns that can be minted.
assert (mint(gov, amount)) # Beosin (Chengdu LianAn) # Mint tokens to the Governance contract.
Put(ctx, START_OFFSET, endOffset) # Beosin (Chengdu LianAn) # Update START_OFFSET to endOffse
t.
return amount + 0
# Beosin (Chengdu LianAn) # The function 'setGovernanceAddress' is defined to set the Governance contrac
t address.
def setGovernanceAddress(governanceAddress):
operator = Get(ctx, OPERATOR)
assert (CheckWitness(operator)) # Beosin (Chengdu LianAn) # Require that only the operator address can
call this function.
if len(governanceAddress) != 20:
raise Exception("address length error")
Put(ctx, GOVERNANCE_ADDRESS, governanceAddress)
return True
# Beosin (Chengdu LianAn) # The function 'getGovernanceAddress' is defined to get the set Governance con
tract address.
def getGovernanceAddress():
return Get(ctx, GOVERNANCE_ADDRESS)
# Beosin (Chengdu LianAn) # The function 'disableUnboundToken' is defined to disable the token mint func
tion.
def disableUnboundToken():
operator = Get(ctx, OPERATOR)
if CheckWitness(operator) == False:
return False
Put(ctx, UNBOUNT_STATUS, True)
return True
# Beosin (Chengdu LianAn) # The function 'enableUnboundToken' is defined to enable the token mint functi
on.
def enableUnboundToken():
operator = Get(ctx, OPERATOR)
if CheckWitness(operator) == False:
return False
Put(ctx, UNBOUNT_STATUS, False)
return True
# Beosin (Chengdu LianAn) # The function 'setPendingOperator' is defined to set the pending operator addr
ess.
def setPendingOperator(operator):
origin = Get(ctx, OPERATOR)
assert (CheckWitness(origin))
assert (len(operator) == 20)
Put(ctx, PENDING_OPERATOR, operator)
return True
# Beosin (Chengdu LianAn) # The function 'getPendingOperator' is defined to get the pending operator add
ress.
def getPendingOperator():
return Get(ctx, PENDING_OPERATOR)
# Beosin (Chengdu LianAn) # The function 'confirmPendingOperator' is defined for the pending operator a
ddress to confirm the operator permission.
def confirmPendingOperator():
pendingOperator = Get(ctx, PENDING_OPERATOR)
assert (CheckWitness(pendingOperator))
Delete(ctx, PENDING_OPERATOR)
Put(ctx, OPERATOR, pendingOperator)
return True
# Beosin (Chengdu LianAn) # The function 'migrateContract' is defined to migrate contract.
def migrateContract(code, needStorage, name, version, author, email, description):
operator = Get(ctx, OPERATOR)
assert (CheckWitness(operator))
res = Migrate(code, needStorage, name, version, author, email, description)
assert (res)
Notify(["Migrate Contract successfully", operator, GetTime()])
return True
# Beosin (Chengdu LianAn) # The function 'mint' is defined to issue tokens to the specified address.
def mint(address, amount):
if amount <= 0:
return False
balance = balanceOf(address)
supply = totalSupply()
Put(ctx, SUPPLY_KEY, supply + amount)
Put(ctx, concat(BALANCE_PREFIX, address), balance + amount)
TransferEvent("", address, amount)
return True
Official Website
https://lianantech.com
E-mail
[email protected]
Twitter
https://twitter.com/Beosin_com