tx · 8ENvEpigvSfsimTjmkHtE7Cmu1fH61nx1gNHCDtba8Te

3PR7vWpRo7VziGSEAaBwb8SuZ7ZzANBCAXr:  -0.01400000 Waves

2022.12.26 12:24 [3442560] smart account 3PR7vWpRo7VziGSEAaBwb8SuZ7ZzANBCAXr > SELF 0.00000000 Waves

{ "type": 13, "id": "8ENvEpigvSfsimTjmkHtE7Cmu1fH61nx1gNHCDtba8Te", "fee": 1400000, "feeAssetId": null, "timestamp": 1672046680854, "version": 2, "chainId": 87, "sender": "3PR7vWpRo7VziGSEAaBwb8SuZ7ZzANBCAXr", "senderPublicKey": "BB3kyd8oPA6pCWSF1SXLKDFQ8cGqrPm7RDrXu49GTRv5", "proofs": [ "3ne9dd2SG7cs48MQKrsLmo8fkrw2ZkVdJzigknyfv1eN5KucQfbqx8AkZ3VS7vSUqC4voLTdykgA8ciKDBvKWR9Q" ], "script": "base64:BgIVCAISBwoFCAEBAQgSAwoBCBIDCgEIDwAJU0VQQVJBVE9SAgJfXwAFV0FWRVMCBVdBVkVTAAxLRVlfTVVMVElTSUcCCE1VTFRJU0lHAApLRVlfU1RBVFVTAgZTVEFUVVMACUtFWV9BU1NFVAIFQVNTRVQAD0tFWV9TUE9OU09SU0hJUAILU1BPTlNPUlNISVAACklOVk9LRV9GRUUAoMIeAAxDT01QRU5TQVRJT04JAGgCAAIFCklOVk9LRV9GRUUAF0ZVTkNfVVBEQVRFX1NQT05TT1JTSElQAhF1cGRhdGVTcG9uc29yc2hpcAANRlVOQ19XSVRIRFJBVwIId2l0aGRyYXcBEF92YWxpZGF0ZUFkZHJlc3MBCGFkZHJlc3NfBAckbWF0Y2gwCQCmCAEFCGFkZHJlc3NfAwkAAQIFByRtYXRjaDACB0FkZHJlc3MEAWEFByRtYXRjaDAGBwEOX3RvQXNzZXRWZWN0b3IBBmFzc2V0XwMJAAACBQZhc3NldF8FBVdBVkVTBQR1bml0CQDZBAEFBmFzc2V0XwEQX2xvYWRTcG9uc29yc2hpcAEOdG9rZW5Db250cmFjdF8EByRtYXRjaDAJAKIIAQkAuQkCCQDMCAIFD0tFWV9TUE9OU09SU0hJUAkAzAgCBQ50b2tlbkNvbnRyYWN0XwUDbmlsBQlTRVBBUkFUT1IDCQABAgUHJG1hdGNoMAIGU3RyaW5nBAFhBQckbWF0Y2gwBAZzdHJ1Y3QJALUJAgUBYQUJU0VQQVJBVE9SCQCYCgYJAJEDAgUGc3RydWN0AAAJAJEDAgUGc3RydWN0AAEJAQ1wYXJzZUludFZhbHVlAQkAkQMCBQZzdHJ1Y3QAAgkBDXBhcnNlSW50VmFsdWUBCQCRAwIFBnN0cnVjdAADCQENcGFyc2VJbnRWYWx1ZQEJAJEDAgUGc3RydWN0AAQJAJEDAgUGc3RydWN0AAUJAAIBAiBfbG9hZFNwb25zb3JzaGlwOiBubyBzcG9uc29yc2hpcAEQX3NhdmVTcG9uc29yc2hpcAEMc3BvbnNvcnNoaXBfCQDMCAIJAQtTdHJpbmdFbnRyeQIJALkJAgkAzAgCBQ9LRVlfU1BPTlNPUlNISVAJAMwIAggFDHNwb25zb3JzaGlwXwJfMQUDbmlsBQlTRVBBUkFUT1IJALkJAgkAzAgCCAUMc3BvbnNvcnNoaXBfAl8xCQDMCAIIBQxzcG9uc29yc2hpcF8CXzIJAMwIAgkApAMBCAUMc3BvbnNvcnNoaXBfAl8zCQDMCAIJAKQDAQgFDHNwb25zb3JzaGlwXwJfNAkAzAgCCQCkAwEIBQxzcG9uc29yc2hpcF8CXzUJAMwIAggFDHNwb25zb3JzaGlwXwJfNgUDbmlsBQlTRVBBUkFUT1IFA25pbAEKX2xvYWRBc3NldAENdG9rZW5Db250cmFjdAQHJG1hdGNoMAkAnQgCBQ10b2tlbkNvbnRyYWN0BQlLRVlfQVNTRVQDCQABAgUHJG1hdGNoMAIGU3RyaW5nBAFhBQckbWF0Y2gwCQDZBAEFAWEBAAMBaQEQc2V0dXBTcG9uc29yc2hpcAUOdG9rZW5Db250cmFjdF8VbWluU3BvbnNvcmVkQXNzZXRGZWVfD3Nwb25zb3JlZFdhdmVzXw90aHJlc2hvbGRXYXZlc18TYmVuZWZpY2lhcnlBZGRyZXNzXwQDZXJyAwkBAiE9AggFAWkGY2FsbGVyBQR0aGlzCQACAQIjc2V0dXBTcG9uc29yc2hpcDogcGVybWlzc2lvbiBkZW5pZWQDCQEBIQEJARBfdmFsaWRhdGVBZGRyZXNzAQUOdG9rZW5Db250cmFjdF8JAAIBAihzZXR1cFNwb25zb3JzaGlwOiBpbnZhbGlkIHRva2VuIGNvbnRyYWN0AwkAZgIAAAUVbWluU3BvbnNvcmVkQXNzZXRGZWVfCQACAQIxc2V0dXBTcG9uc29yc2hpcDogaW52YWxpZCBtaW4gc3BvbnNvcmVkIGFzc2V0IGZlZQMJAGYCAAAFD3Nwb25zb3JlZFdhdmVzXwkAAgECKXNldHVwU3BvbnNvcnNoaXA6IGludmFsaWQgc3BvbnNvcmVkIHdhdmVzAwkAZgIAAAUPdGhyZXNob2xkV2F2ZXNfCQACAQIpc2V0dXBTcG9uc29yc2hpcDogaW52YWxpZCB0aHJlc2hvbGQgd2F2ZXMDCQEBIQEJARBfdmFsaWRhdGVBZGRyZXNzAQUTYmVuZWZpY2lhcnlBZGRyZXNzXwkAAgECLXNldHVwU3BvbnNvcnNoaXA6IGludmFsaWQgYmVuZWZpY2lhcnkgYWRkcmVzcwUEdW5pdAMJAAACBQNlcnIFA2VycgQNdG9rZW5Db250cmFjdAkBEUBleHRyTmF0aXZlKDEwNjIpAQUOdG9rZW5Db250cmFjdF8ECnRva2VuQXNzZXQJAQpfbG9hZEFzc2V0AQUNdG9rZW5Db250cmFjdAQLd2F2ZXNBbW91bnQICQDvBwEFDXRva2VuQ29udHJhY3QHcmVndWxhcgQNd2F2ZXNUb0NoYXJnZQMJAGYCBQ9zcG9uc29yZWRXYXZlc18FC3dhdmVzQW1vdW50CQBlAgUPc3BvbnNvcmVkV2F2ZXNfBQt3YXZlc0Ftb3VudAAABAppbnZvY2F0aW9uCQD8BwQFDXRva2VuQ29udHJhY3QFF0ZVTkNfVVBEQVRFX1NQT05TT1JTSElQCQDMCAIFFW1pblNwb25zb3JlZEFzc2V0RmVlXwkAzAgCBQ9zcG9uc29yZWRXYXZlc18FA25pbAUDbmlsAwkAAAIFCmludm9jYXRpb24FCmludm9jYXRpb24EC3Nwb25zb3JzaGlwCQCYCgYFDnRva2VuQ29udHJhY3RfCQDYBAEFCnRva2VuQXNzZXQFFW1pblNwb25zb3JlZEFzc2V0RmVlXwUPc3BvbnNvcmVkV2F2ZXNfBQ90aHJlc2hvbGRXYXZlc18FE2JlbmVmaWNpYXJ5QWRkcmVzc18JAJQKAgkAzggCCQEQX3NhdmVTcG9uc29yc2hpcAEFC3Nwb25zb3JzaGlwCQDMCAIJAQ5TY3JpcHRUcmFuc2ZlcgMFDXRva2VuQ29udHJhY3QFDXdhdmVzVG9DaGFyZ2UFBHVuaXQFA25pbAUEdW5pdAkAAgECJFN0cmljdCB2YWx1ZSBpcyBub3QgZXF1YWwgdG8gaXRzZWxmLgkAAgECJFN0cmljdCB2YWx1ZSBpcyBub3QgZXF1YWwgdG8gaXRzZWxmLgFpARVjaGVja3BvaW50U3BvbnNvcnNoaXABDnRva2VuQ29udHJhY3RfBA10b2tlbkNvbnRyYWN0CQETdmFsdWVPckVycm9yTWVzc2FnZQIJAKYIAQUOdG9rZW5Db250cmFjdF8CLWNoZWNrcG9pbnRTcG9uc29yc2hpcDogaW52YWxpZCB0b2tlbiBjb250cmFjdAQLc3BvbnNvcnNoaXAJARBfbG9hZFNwb25zb3JzaGlwAQUOdG9rZW5Db250cmFjdF8ECnRva2VuQXNzZXQIBQtzcG9uc29yc2hpcAJfMgQUbWluU3BvbnNvcmVkQXNzZXRGZWUIBQtzcG9uc29yc2hpcAJfMwQOc3BvbnNvcmVkV2F2ZXMIBQtzcG9uc29yc2hpcAJfNAQOdGhyZXNob2xkV2F2ZXMIBQtzcG9uc29yc2hpcAJfNQQSYmVuZWZpY2lhcnlBZGRyZXNzCQERQGV4dHJOYXRpdmUoMTA2MikBCAULc3BvbnNvcnNoaXACXzYECXdhdmVzTGVmdAgJAO8HAQUNdG9rZW5Db250cmFjdAdyZWd1bGFyBANlcnIDCQBmAgUJd2F2ZXNMZWZ0BQ50aHJlc2hvbGRXYXZlcwkAAgECNGNoZWNrcG9pbnRTcG9uc29yc2hpcDogbm8gdGhyZXNob2xkIGhhcyBiZWVuIHJlYWNoZWQFBHVuaXQDCQAAAgUDZXJyBQNlcnIECmludm9jYXRpb24JAPwHBAUNdG9rZW5Db250cmFjdAUXRlVOQ19VUERBVEVfU1BPTlNPUlNISVAJAMwIAgUUbWluU3BvbnNvcmVkQXNzZXRGZWUJAMwIAgUOc3BvbnNvcmVkV2F2ZXMFA25pbAUDbmlsAwkAAAIFCmludm9jYXRpb24FCmludm9jYXRpb24EByRtYXRjaDAFCmludm9jYXRpb24DCQABAgUHJG1hdGNoMAIDSW50BBZzcG9uc29yVG9rZW5Ub0V4Y2hhbmdlBQckbWF0Y2gwBAt3YXZlc0JlZm9yZQgJAO8HAQUEdGhpcwdyZWd1bGFyAwkAAAIFC3dhdmVzQmVmb3JlBQt3YXZlc0JlZm9yZQQIZXhjaGFuZ2UJAPwHBAUNdG9rZW5Db250cmFjdAUNRlVOQ19XSVRIRFJBVwUDbmlsCQDMCAIJAQ9BdHRhY2hlZFBheW1lbnQCCQEOX3RvQXNzZXRWZWN0b3IBBQp0b2tlbkFzc2V0BRZzcG9uc29yVG9rZW5Ub0V4Y2hhbmdlBQNuaWwDCQAAAgUIZXhjaGFuZ2UFCGV4Y2hhbmdlBAp3YXZlc0FmdGVyCAkA7wcBBQR0aGlzB3JlZ3VsYXIDCQAAAgUKd2F2ZXNBZnRlcgUKd2F2ZXNBZnRlcgQOd2F2ZXNSZW1haW5pbmcICQDvBwEFDXRva2VuQ29udHJhY3QHcmVndWxhcgMJAAACBQ53YXZlc1JlbWFpbmluZwUOd2F2ZXNSZW1haW5pbmcEC3dhdmVzSW5jb21lCQBlAgUKd2F2ZXNBZnRlcgULd2F2ZXNCZWZvcmUEDXdhdmVzVG9DaGFyZ2UDCQBmAgUOc3BvbnNvcmVkV2F2ZXMFDndhdmVzUmVtYWluaW5nCQBlAgUOc3BvbnNvcmVkV2F2ZXMFDndhdmVzUmVtYWluaW5nAAAEEndhdmVzVG9CZW5lZmljaWFyeQMJAGYCBQt3YXZlc0luY29tZQUNd2F2ZXNUb0NoYXJnZQkAZQIFC3dhdmVzSW5jb21lBQ13YXZlc1RvQ2hhcmdlAAAEDGNvbXBlbnNhdGlvbgMJAGYCBRJ3YXZlc1RvQmVuZWZpY2lhcnkFDENPTVBFTlNBVElPTgUMQ09NUEVOU0FUSU9OAAAJAJQKAgkAzAgCCQEOU2NyaXB0VHJhbnNmZXIDBQ10b2tlbkNvbnRyYWN0BQ13YXZlc1RvQ2hhcmdlBQR1bml0CQDMCAIJAQ5TY3JpcHRUcmFuc2ZlcgMFEmJlbmVmaWNpYXJ5QWRkcmVzcwkAZQIFEndhdmVzVG9CZW5lZmljaWFyeQUMY29tcGVuc2F0aW9uBQR1bml0CQDMCAIJAQ5TY3JpcHRUcmFuc2ZlcgMIBQFpDG9yaWdpbkNhbGxlcgUMY29tcGVuc2F0aW9uBQR1bml0BQNuaWwFBHVuaXQJAAIBAiRTdHJpY3QgdmFsdWUgaXMgbm90IGVxdWFsIHRvIGl0c2VsZi4JAAIBAiRTdHJpY3QgdmFsdWUgaXMgbm90IGVxdWFsIHRvIGl0c2VsZi4JAAIBAiRTdHJpY3QgdmFsdWUgaXMgbm90IGVxdWFsIHRvIGl0c2VsZi4JAAIBAiRTdHJpY3QgdmFsdWUgaXMgbm90IGVxdWFsIHRvIGl0c2VsZi4JAAIBAidjaGVja3BvaW50U3BvbnNvcnNoaXA6IGludm9jYXRpb24gZXJyb3IJAAIBAiRTdHJpY3QgdmFsdWUgaXMgbm90IGVxdWFsIHRvIGl0c2VsZi4JAAIBAiRTdHJpY3QgdmFsdWUgaXMgbm90IGVxdWFsIHRvIGl0c2VsZi4BaQELc2V0TXVsdGlzaWcBCW11bHRpc2lnXwQDZXJyAwkBAiE9AggFAWkGY2FsbGVyBQR0aGlzCQACAQIec2V0TXVsdGlzaWc6IHBlcm1pc3Npb24gZGVuaWVkAwkBASEBCQEQX3ZhbGlkYXRlQWRkcmVzcwEFCW11bHRpc2lnXwkAAgECJXNldE11bHRpc2lnOiBpbnZhbGlkIG11bHRpc2lnIGFkZHJlc3MFBHVuaXQDCQAAAgUDZXJyBQNlcnIJAJQKAgkAzAgCCQELU3RyaW5nRW50cnkCBQxLRVlfTVVMVElTSUcFCW11bHRpc2lnXwUDbmlsBQR1bml0CQACAQIkU3RyaWN0IHZhbHVlIGlzIG5vdCBlcXVhbCB0byBpdHNlbGYuAQJ0eAEGdmVyaWZ5AAQHJG1hdGNoMAkAoggBBQxLRVlfTVVMVElTSUcDCQABAgUHJG1hdGNoMAIGU3RyaW5nBAhtdWx0aXNpZwUHJG1hdGNoMAkBC3ZhbHVlT3JFbHNlAgkAmwgCCQERQGV4dHJOYXRpdmUoMTA2MikBBQhtdWx0aXNpZwkAuQkCCQDMCAIFCktFWV9TVEFUVVMJAMwIAgkApQgBBQR0aGlzCQDMCAIJANgEAQgFAnR4AmlkBQNuaWwFCVNFUEFSQVRPUgcJAPQDAwgFAnR4CWJvZHlCeXRlcwkAkQMCCAUCdHgGcHJvb2ZzAAAIBQJ0eA9zZW5kZXJQdWJsaWNLZXmjflNS", "height": 3442560, "applicationStatus": "succeeded", "spentComplexity": 0 } View: original | compacted Prev: none Next: none Full:
OldNewDifferences
1-# no script
1+{-# STDLIB_VERSION 6 #-}
2+{-# SCRIPT_TYPE ACCOUNT #-}
3+{-# CONTENT_TYPE DAPP #-}
4+let SEPARATOR = "__"
5+
6+let WAVES = "WAVES"
7+
8+let KEY_MULTISIG = "MULTISIG"
9+
10+let KEY_STATUS = "STATUS"
11+
12+let KEY_ASSET = "ASSET"
13+
14+let KEY_SPONSORSHIP = "SPONSORSHIP"
15+
16+let INVOKE_FEE = 500000
17+
18+let COMPENSATION = (2 * INVOKE_FEE)
19+
20+let FUNC_UPDATE_SPONSORSHIP = "updateSponsorship"
21+
22+let FUNC_WITHDRAW = "withdraw"
23+
24+func _validateAddress (address_) = match addressFromString(address_) {
25+ case a: Address =>
26+ true
27+ case _ =>
28+ false
29+}
30+
31+
32+func _toAssetVector (asset_) = if ((asset_ == WAVES))
33+ then unit
34+ else fromBase58String(asset_)
35+
36+
37+func _loadSponsorship (tokenContract_) = match getString(makeString([KEY_SPONSORSHIP, tokenContract_], SEPARATOR)) {
38+ case a: String =>
39+ let struct = split(a, SEPARATOR)
40+ $Tuple6(struct[0], struct[1], parseIntValue(struct[2]), parseIntValue(struct[3]), parseIntValue(struct[4]), struct[5])
41+ case _ =>
42+ throw("_loadSponsorship: no sponsorship")
43+}
44+
45+
46+func _saveSponsorship (sponsorship_) = [StringEntry(makeString([KEY_SPONSORSHIP, sponsorship_._1], SEPARATOR), makeString([sponsorship_._1, sponsorship_._2, toString(sponsorship_._3), toString(sponsorship_._4), toString(sponsorship_._5), sponsorship_._6], SEPARATOR))]
47+
48+
49+func _loadAsset (tokenContract) = match getString(tokenContract, KEY_ASSET) {
50+ case a: String =>
51+ fromBase58String(a)
52+ case _ =>
53+ base58''
54+}
55+
56+
57+@Callable(i)
58+func setupSponsorship (tokenContract_,minSponsoredAssetFee_,sponsoredWaves_,thresholdWaves_,beneficiaryAddress_) = {
59+ let err = if ((i.caller != this))
60+ then throw("setupSponsorship: permission denied")
61+ else if (!(_validateAddress(tokenContract_)))
62+ then throw("setupSponsorship: invalid token contract")
63+ else if ((0 > minSponsoredAssetFee_))
64+ then throw("setupSponsorship: invalid min sponsored asset fee")
65+ else if ((0 > sponsoredWaves_))
66+ then throw("setupSponsorship: invalid sponsored waves")
67+ else if ((0 > thresholdWaves_))
68+ then throw("setupSponsorship: invalid threshold waves")
69+ else if (!(_validateAddress(beneficiaryAddress_)))
70+ then throw("setupSponsorship: invalid beneficiary address")
71+ else unit
72+ if ((err == err))
73+ then {
74+ let tokenContract = addressFromStringValue(tokenContract_)
75+ let tokenAsset = _loadAsset(tokenContract)
76+ let wavesAmount = wavesBalance(tokenContract).regular
77+ let wavesToCharge = if ((sponsoredWaves_ > wavesAmount))
78+ then (sponsoredWaves_ - wavesAmount)
79+ else 0
80+ let invocation = invoke(tokenContract, FUNC_UPDATE_SPONSORSHIP, [minSponsoredAssetFee_, sponsoredWaves_], nil)
81+ if ((invocation == invocation))
82+ then {
83+ let sponsorship = $Tuple6(tokenContract_, toBase58String(tokenAsset), minSponsoredAssetFee_, sponsoredWaves_, thresholdWaves_, beneficiaryAddress_)
84+ $Tuple2((_saveSponsorship(sponsorship) ++ [ScriptTransfer(tokenContract, wavesToCharge, unit)]), unit)
85+ }
86+ else throw("Strict value is not equal to itself.")
87+ }
88+ else throw("Strict value is not equal to itself.")
89+ }
90+
91+
92+
93+@Callable(i)
94+func checkpointSponsorship (tokenContract_) = {
95+ let tokenContract = valueOrErrorMessage(addressFromString(tokenContract_), "checkpointSponsorship: invalid token contract")
96+ let sponsorship = _loadSponsorship(tokenContract_)
97+ let tokenAsset = sponsorship._2
98+ let minSponsoredAssetFee = sponsorship._3
99+ let sponsoredWaves = sponsorship._4
100+ let thresholdWaves = sponsorship._5
101+ let beneficiaryAddress = addressFromStringValue(sponsorship._6)
102+ let wavesLeft = wavesBalance(tokenContract).regular
103+ let err = if ((wavesLeft > thresholdWaves))
104+ then throw("checkpointSponsorship: no threshold has been reached")
105+ else unit
106+ if ((err == err))
107+ then {
108+ let invocation = invoke(tokenContract, FUNC_UPDATE_SPONSORSHIP, [minSponsoredAssetFee, sponsoredWaves], nil)
109+ if ((invocation == invocation))
110+ then match invocation {
111+ case sponsorTokenToExchange: Int =>
112+ let wavesBefore = wavesBalance(this).regular
113+ if ((wavesBefore == wavesBefore))
114+ then {
115+ let exchange = invoke(tokenContract, FUNC_WITHDRAW, nil, [AttachedPayment(_toAssetVector(tokenAsset), sponsorTokenToExchange)])
116+ if ((exchange == exchange))
117+ then {
118+ let wavesAfter = wavesBalance(this).regular
119+ if ((wavesAfter == wavesAfter))
120+ then {
121+ let wavesRemaining = wavesBalance(tokenContract).regular
122+ if ((wavesRemaining == wavesRemaining))
123+ then {
124+ let wavesIncome = (wavesAfter - wavesBefore)
125+ let wavesToCharge = if ((sponsoredWaves > wavesRemaining))
126+ then (sponsoredWaves - wavesRemaining)
127+ else 0
128+ let wavesToBeneficiary = if ((wavesIncome > wavesToCharge))
129+ then (wavesIncome - wavesToCharge)
130+ else 0
131+ let compensation = if ((wavesToBeneficiary > COMPENSATION))
132+ then COMPENSATION
133+ else 0
134+ $Tuple2([ScriptTransfer(tokenContract, wavesToCharge, unit), ScriptTransfer(beneficiaryAddress, (wavesToBeneficiary - compensation), unit), ScriptTransfer(i.originCaller, compensation, unit)], unit)
135+ }
136+ else throw("Strict value is not equal to itself.")
137+ }
138+ else throw("Strict value is not equal to itself.")
139+ }
140+ else throw("Strict value is not equal to itself.")
141+ }
142+ else throw("Strict value is not equal to itself.")
143+ case _ =>
144+ throw("checkpointSponsorship: invocation error")
145+ }
146+ else throw("Strict value is not equal to itself.")
147+ }
148+ else throw("Strict value is not equal to itself.")
149+ }
150+
151+
152+
153+@Callable(i)
154+func setMultisig (multisig_) = {
155+ let err = if ((i.caller != this))
156+ then throw("setMultisig: permission denied")
157+ else if (!(_validateAddress(multisig_)))
158+ then throw("setMultisig: invalid multisig address")
159+ else unit
160+ if ((err == err))
161+ then $Tuple2([StringEntry(KEY_MULTISIG, multisig_)], unit)
162+ else throw("Strict value is not equal to itself.")
163+ }
164+
165+
166+@Verifier(tx)
167+func verify () = match getString(KEY_MULTISIG) {
168+ case multisig: String =>
169+ valueOrElse(getBoolean(addressFromStringValue(multisig), makeString([KEY_STATUS, toString(this), toBase58String(tx.id)], SEPARATOR)), false)
170+ case _ =>
171+ sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
172+}
173+

github/deemru/w8io/6500d08 
16.29 ms