-
Notifications
You must be signed in to change notification settings - Fork 2
/
standard_token.lua
120 lines (113 loc) · 4.92 KB
/
standard_token.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
local type_check = {}
function type_check.isValidAddress(address)
-- check existence of invalid alphabets
if nil ~= string.match(address, '[^123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]') then
return false
end
-- check lenght is in range
if 52 ~= string.len(address) then
return false
end
-- TODO add checksum verification?
return true
end
function type_check.isValidNumber(value)
if nil ~= string.match(value, '[^0123456789]') then
return false
end
return true
end
state.var {
Symbol = state.value(),
Name = state.value(),
Decimals = state.value(),
TotalSupply = state.value(),
Balances = state.map(),
Nonces = state.map(),
-- Contract ID is a unique id that cannot be shared by another contract, even one on a sidechain
-- This is neeeded for replay protection of signed transfer, because users might have the same private key
-- on different sidechains
ContractID = state.value(),
}
function constructor(total_supply, receiver)
assert(type_check.isValidAddress(receiver), "invalid address format: "..receiver)
Symbol:set("TOKEN")
Name:set("Standard Token on Aergo")
Decimals:set(18)
total_supply = bignum.number(total_supply)
TotalSupply:set(total_supply)
Balances[receiver] = total_supply
-- contractID is the hash of system.getContractID (prevent replay between contracts on the same chain) and system.getPrevBlockHash (prevent replay between sidechains).
-- take the first 16 bytes to save size of signed message
local id = crypto.sha256(system.getContractID()..system.getPrevBlockHash())
id = string.sub(id, 3, 32)
ContractID:set(id)
return id
end
---------------------------------------
-- Transfer sender's token to target 'to'
-- @type call
-- @param to a target address
-- @param value string amount of tokens to send
-- @return success
---------------------------------------
function transfer(to, value)
assert(type_check.isValidNumber(value), "invalid value format (must be string)")
assert(type_check.isValidAddress(to), "invalid address format: " .. to)
local from = system.getSender()
local bvalue = bignum.number(value)
local b0 = bignum.number(0)
assert(bvalue > b0, "invalid value")
assert(to ~= from, "same sender and receiver")
assert(Balances[from] and bvalue <= Balances[from], "not enough balance")
Balances[from] = Balances[from] - bvalue
Nonces[from] = (Nonces[from] or 0) + 1
Balances[to] = (Balances[to] or b0) + bvalue
-- TODO event notification
return true
end
---------------------------------------
-- Transfer tokens according to signed data from the owner
-- @type call
-- @param from sender's address
-- @param to receiver's address
-- @param value string amount of token to send in aer
-- @param nonce string nonce of the sender to prevent replay
-- @param fee string fee given to the tx broadcaster
-- @param deadline block number before which the tx can be executed
-- @param signature signature proving sender's consent
-- @return success
-- This function is intended for feeless token transfers with a tx broadcaster. Make sure that 'from' is the system.getSender() of the caller contract.
---------------------------------------
function signed_transfer(from, to, value, nonce, signature, fee, deadline)
assert(type_check.isValidNumber(value), "invalid value format (must be string)")
assert(type_check.isValidNumber(fee), "invalid fee format (must be string)")
local bfee = bignum.number(fee)
local bvalue = bignum.number(value)
local b0 = bignum.number(0)
-- check addresses
assert(type_check.isValidAddress(to), "invalid address format: " .. to)
assert(type_check.isValidAddress(from), "invalid address format: " .. from)
assert(to ~= from, "same sender and receiver")
-- check amounts, fee
assert(bfee >= b0, "fee must be positive")
assert(bvalue >= b0, "value must be positive")
assert(Balances[from] and (bvalue+bfee) <= Balances[from], "not enough balance")
-- check deadline
assert(deadline == 0 or system.getBlockheight() < deadline, "deadline has passed")
-- check nonce
if Nonces[from] == nil then Nonces[from] = 0 end
assert(Nonces[from] == nonce, "nonce is invalid or already spent")
-- construct signed transfer and verifiy signature
data = crypto.sha256(to..bignum.tostring(bvalue)..tostring(nonce)..bignum.tostring(bfee)..tostring(deadline)..ContractID:get())
assert(crypto.ecverify(data, signature, from), "signature of signed transfer is invalid")
-- execute transfer
Balances[from] = Balances[from] - bvalue - bfee
Balances[to] = (Balances[to] or b0) + bvalue
Balances[system.getOrigin()] = (Balances[system.getOrigin()] or b0) + bfee
Nonces[from] = Nonces[from] + 1
-- TODO event notification
return true
end
-- register functions to abi
abi.register(transfer, signed_transfer)