From d750a462a69598307d5b0f9fca782269d2add8b7 Mon Sep 17 00:00:00 2001 From: siovanus Date: Tue, 19 May 2020 13:49:11 +0800 Subject: [PATCH] add oracle contract written by ont smartcontract --- ont-contracts/README.md | 0 ont-contracts/chainlink.py | 100 ++++++++ ont-contracts/chainlink_client.py | 202 ++++++++++++++++ ont-contracts/demo/chainlink_example.py | 88 +++++++ ont-contracts/demo/test.py | 61 +++++ ont-contracts/erc677.py | 308 ++++++++++++++++++++++++ ont-contracts/lib/CBOR.py | 121 ++++++++++ ont-contracts/lib/ZeroCopySink.py | 154 ++++++++++++ ont-contracts/lib/ZeroCopySource.py | 202 ++++++++++++++++ ont-contracts/lib/cbor_test.py | 0 ont-contracts/lib/sink_source_test.py | 121 ++++++++++ ont-contracts/oracle.py | 278 +++++++++++++++++++++ 12 files changed, 1635 insertions(+) create mode 100644 ont-contracts/README.md create mode 100644 ont-contracts/chainlink.py create mode 100644 ont-contracts/chainlink_client.py create mode 100644 ont-contracts/demo/chainlink_example.py create mode 100644 ont-contracts/demo/test.py create mode 100644 ont-contracts/erc677.py create mode 100644 ont-contracts/lib/CBOR.py create mode 100644 ont-contracts/lib/ZeroCopySink.py create mode 100644 ont-contracts/lib/ZeroCopySource.py create mode 100644 ont-contracts/lib/cbor_test.py create mode 100644 ont-contracts/lib/sink_source_test.py create mode 100644 ont-contracts/oracle.py diff --git a/ont-contracts/README.md b/ont-contracts/README.md new file mode 100644 index 0000000..e69de29 diff --git a/ont-contracts/chainlink.py b/ont-contracts/chainlink.py new file mode 100644 index 0000000..6493104 --- /dev/null +++ b/ont-contracts/chainlink.py @@ -0,0 +1,100 @@ +############ Chainlink Library######### +from ontology.interop.System.App import RegisterAppCall + +defaultBufferSize = 256 +BufferCall = RegisterAppCall('d5f12664535717af51f52fe2aa88a18d327ea1b9', 'operation', 'args') +CBORCall = RegisterAppCall('3f75e2814021abed8a616da8d408d1347cac988f', 'operation', 'args') + + +def Main(operation, args): + if operation == 'initialize': + assert (len(args) == 3) + id = args[0] + callbackAddress = args[1] + callbackFunction = args[2] + return initialize(id, callbackAddress, callbackFunction) + + if operation == 'setBuffer': + assert (len(args) == 2) + request = args[0] + data = args[1] + return setBuffer(request, data) + + if operation == 'add': + assert (len(args) == 3) + request = args[0] + key = args[1] + value = args[2] + return add(request, key, value) + + if operation == 'addBytes': + assert (len(args) == 3) + request = args[0] + key = args[1] + value = args[2] + return addBytes(request, key, value) + + if operation == 'addUInt': + assert (len(args) == 3) + request = args[0] + key = args[1] + value = args[2] + return addUInt(request, key, value) + + if operation == 'addInt': + assert (len(args) == 3) + request = args[0] + key = args[1] + value = args[2] + return addInt(request, key, value) + + if operation == 'addStringArray': + assert (len(args) == 3) + request = args[0] + key = args[1] + values = args[2] + return addStringArray(request, key, values) + + return True + + +def initialize(id, callbackAddress, callbackFunction): + return [id, callbackAddress, callbackFunction, 0, None] + + +def setBuffer(request, data): + request[4] = BufferCall('WriteBytes', [data, request[4]]) + return [request[0], request[1], request[2], request[3], request[4]] + + +def add(request, key, value): + request[4] = CBORCall('encodeString', [request[4], key]) + request[4] = CBORCall('encodeString', [request[4], value]) + return [request[0], request[1], request[2], request[3], request[4]] + + +def addBytes(request, key, value): + request[4] = CBORCall('encodeString', [request[4], key]) + request[4] = CBORCall('encodeBytes', [request[4], value]) + return [request[0], request[1], request[2], request[3], request[4]] + + +def addInt(request, key, value): + request[4] = CBORCall('encodeString', [request[4], key]) + request[4] = CBORCall('encodeInt', [request[4], value]) + return [request[0], request[1], request[2], request[3], request[4]] + + +def addUInt(request, key, value): + request[4] = CBORCall('encodeString', [request[4], key]) + request[4] = CBORCall('encodeUInt', [request[4], value]) + return [request[0], request[1], request[2], request[3], request[4]] + + +def addStringArray(request, key, values): + request[4] = CBORCall('encodeString', [request[4], key]) + request[4] = CBORCall('startArray', [request[4]]) + for i in range(len(values)): + request[4] = CBORCall('encodeString', [request[4], values[i]]) + request[4] = CBORCall('endSequence', [request[4]]) + return [request[0], request[1], request[2], request[3], request[4]] diff --git a/ont-contracts/chainlink_client.py b/ont-contracts/chainlink_client.py new file mode 100644 index 0000000..c827dcf --- /dev/null +++ b/ont-contracts/chainlink_client.py @@ -0,0 +1,202 @@ +from ontology.builtins import sha256, concat +from ontology.interop.Ontology.Runtime import Base58ToAddress +from ontology.interop.System.Action import RegisterAction +from ontology.interop.System.App import DynamicAppCall, RegisterAppCall +from ontology.interop.System.ExecutionEngine import GetExecutingScriptHash, GetCallingScriptHash +from ontology.interop.System.Runtime import CheckWitness, Serialize, Notify +from ontology.interop.System.Storage import GetContext, Get, Put, Delete +from ontology.libont import bytearray_reverse +# bb7513b23fae0117eae21eea2cd732e1cc267e7e +AMOUNT_OVERRIDE = 0 + +SENDER_OVERRIDE = 0 + +ARGS_VERSION = 1 + +REQUEST_COUNT = "RequestCount" + +ORACLE_ADDRESS = "oracle" +LINK_ADDRESS = "link" + +PENDING_REQUESTS_PREFIX = "pendingRequests" + +contractHash = GetExecutingScriptHash() + +ChainlinkRequestedEvent = RegisterAction("chainlinkRequestedEvent", "requestId") + +ChainlinkFulfilledEvent = RegisterAction("chainlinkFulfilledEvent", "requestId") + +ChainlinkCancelledEvent = RegisterAction("chainlinkCancelledEvent", "requestId") + +ChainlinkCall = RegisterAppCall('db6f26fb0f217d6762aa3d6d38e827789a3128d1', 'operation', 'args') + +OWNER = Base58ToAddress("AbG3ZgFrMK6fqwXWR1WkQ1d1EYVunCwknu") + +def Main(operation, args): + if operation == 'buildChainlinkRequest': + assert (len(args) == 3) + _specId = args[0] + callbackAddress = args[1] + callbackFunction = args[2] + return buildChainlinkRequest(_specId, callbackAddress, callbackFunction) + + if operation == 'sendChainlinkRequest': + assert (len(args) == 3) + caller = args[0] + req = args[1] + payment = args[2] + return sendChainlinkRequest(caller, req, payment) + + if operation == 'sendChainlinkRequestTo': + assert (len(args) == 4) + caller = args[0] + oracle = args[1] + req = args[2] + payment = args[3] + return sendChainlinkRequestTo(caller, oracle, req, payment) + + if operation == 'cancelChainlinkRequest': + assert (len(args) == 5) + sender = args[0] + requestId = args[1] + payment = args[2] + callbackFunctionId = args[3] + expiration = args[4] + return cancelChainlinkRequest(sender, requestId, payment, callbackFunctionId, expiration) + + if operation == 'setChainlinkOracle': + assert (len(args) == 1) + oracle = args[0] + return setChainlinkOracle(oracle) + + if operation == 'setChainlinkToken': + assert (len(args) == 1) + link = args[0] + return setChainlinkToken(link) + + if operation == 'chainlinkTokenAddress': + return chainlinkTokenAddress() + + if operation == 'chainlinkOracleAddress': + return chainlinkOracleAddress() + + if operation == 'addChainlinkExternalRequest': + assert (len(args) == 2) + oracle = args[0] + requestId = args[1] + return addChainlinkExternalRequest(oracle, requestId) + + if operation == 'recordChainlinkFulfillment': + assert (len(args) == 2) + sender = args[0] + requestId = args[1] + return recordChainlinkFulfillment(sender, requestId) + + return False + + +def buildChainlinkRequest(_specId, callbackAddress, callbackFunction): + return ChainlinkCall('initialize', [_specId, callbackAddress, callbackFunction]) + + +def sendChainlinkRequest(caller, req, payment): + return sendChainlinkRequestTo(caller, Get(GetContext(), ORACLE_ADDRESS), req, payment) + +def sendChainlinkRequestTo(caller, oracle, req, payment): + RequireWitness(caller) + requestCount = Get(GetContext(), REQUEST_COUNT) + requestId = sha256(Serialize([req[1], caller, requestCount])) + req[3] = requestCount + Put(GetContext(), concatKey(PENDING_REQUESTS_PREFIX, requestId), oracle) + ChainlinkRequestedEvent(requestId) + link = Get(GetContext(), LINK_ADDRESS) + + params = [caller, oracle, payment, + [SENDER_OVERRIDE, AMOUNT_OVERRIDE, req[0], req[1], req[2], req[3], ARGS_VERSION, req[4], 'oracleRequest']] + assert (DynamicCallFunction(bytearray_reverse(link), "transferAndCall", params)) + + Put(GetContext(), REQUEST_COUNT, requestCount + 1) + return True + + +def cancelChainlinkRequest(sender, requestId, payment, callbackFunctionId, expiration): + RequireWitness(sender) + oracle = Get(GetContext(), concatKey(PENDING_REQUESTS_PREFIX, requestId)) + params = [sender, requestId, payment, GetCallingScriptHash(), callbackFunctionId, expiration] + assert (DynamicCallFunction(bytearray_reverse(oracle), "cancelOracleRequest", params)) + Delete(GetContext(), concatKey(PENDING_REQUESTS_PREFIX, requestId)) + ChainlinkCancelledEvent(requestId) + return True + + +def setChainlinkOracle(oracle): + RequireWitness(OWNER) + assert (len(oracle) == 20) + Put(GetContext(), ORACLE_ADDRESS, oracle) + return True + + +def setChainlinkToken(link): + RequireWitness(OWNER) + assert (len(link) == 20) + Put(GetContext(), LINK_ADDRESS, link) + return True + + +def chainlinkTokenAddress(): + return Get(GetContext(), LINK_ADDRESS) + + +def chainlinkOracleAddress(): + return Get(GetContext(), ORACLE_ADDRESS) + + +def addChainlinkExternalRequest(oracle, requestId): + assert (notPendingRequest(requestId)) + Put(GetContext(), concatKey(PENDING_REQUESTS_PREFIX, requestId), oracle) + return True + + +## TODO +def useChainlinkWithENS(ens, node): + return True + + +## TODO +def updateChainlinkOracleWithENS(): + return True + + +def recordChainlinkFulfillment(sender, requestId): + assert (sender == Get(GetContext(), concatKey(PENDING_REQUESTS_PREFIX, requestId))) + # Notify([sender, Get(GetContext(), concatKey(PENDING_REQUESTS_PREFIX, requestId))]) + Delete(GetContext(), concatKey(PENDING_REQUESTS_PREFIX, requestId)) + ChainlinkFulfilledEvent(requestId) + return True + + +def notPendingRequest(requestId): + assert (not Get(GetContext(), concatKey(PENDING_REQUESTS_PREFIX, requestId))) + return True + + +def concatKey(str1, str2): + return concat(concat(str1, '_'), str2) + + +def RequireScriptHash(key): + assert (len(key) == 20) + return True + + +def RequireWitness(witness): + assert (CheckWitness(witness)) + return True + + +def DynamicCallFunction(callAddress, callbackFunctionId, params): + res = DynamicAppCall(callAddress, callbackFunctionId, params) + if res and res == b'\x01': + return True + else: + return False \ No newline at end of file diff --git a/ont-contracts/demo/chainlink_example.py b/ont-contracts/demo/chainlink_example.py new file mode 100644 index 0000000..8d02c1d --- /dev/null +++ b/ont-contracts/demo/chainlink_example.py @@ -0,0 +1,88 @@ +from ontology.builtins import concat +from ontology.interop.Ontology.Runtime import Base58ToAddress +from ontology.interop.System.App import RegisterAppCall, DynamicAppCall +from ontology.interop.System.ExecutionEngine import GetExecutingScriptHash, GetCallingScriptHash +from ontology.interop.System.Runtime import CheckWitness, Notify +from ontology.interop.System.Storage import GetContext, Put, Get +from ontology.libont import bytearray_reverse + +CURRENT_PRICE = 'CurrentPrice' + +OWNER = Base58ToAddress('AbG3ZgFrMK6fqwXWR1WkQ1d1EYVunCwknu') + +ChainlinkCall = RegisterAppCall('db6f26fb0f217d6762aa3d6d38e827789a3128d1', 'operation', 'args') +ChainlinkClientCall = RegisterAppCall('da8aed3a33ba8e7159a991070ea19002ebb06c6f', 'operation', 'args') + +ContractAddress = GetExecutingScriptHash() + + +def Main(operation, args): + if operation == 'requestEthereumPrice': + assert (len(args) == 3) + oracle = args[0] + jobId = args[1] + payment = args[2] + return requestEthereumPrice(oracle, jobId, payment) + + if operation == 'fulfill': + assert (len(args) == 2) + requestId = args[0] + price = args[1] + return fulfill(requestId, price) + + if operation == 'getCurrentPrice': + return getCurrentPrice() + + if operation == 'cancelRequest': + assert (len(args) == 4) + requestId = args[0] + payment = args[1] + callBackFunc = args[2] + expiration = args[3] + return cancelRequest(requestId, payment, callBackFunc, expiration) + + return False + + +def requestEthereumPrice(oracle, jobId, payment): + assert (CheckWitness(OWNER)) + req = ChainlinkClientCall('buildChainlinkRequest', [jobId, ContractAddress, 'fulfill']) + req = ChainlinkCall('add', [req, "get", "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD"]) + req = ChainlinkCall('add', [req, "path", "USD"]) + req = ChainlinkCall('addInt', [req, "times", 100]) + # Notify([OWNER, oracle, req, payment]) + assert (ChainlinkClientCall('sendChainlinkRequestTo', [OWNER, oracle, req, payment])) + return True + + +def fulfill(requestId, price): + assert (ChainlinkClientCall('recordChainlinkFulfillment', [bytearray_reverse(GetCallingScriptHash()), requestId])) + # Notify(['test']) + Put(GetContext(), CURRENT_PRICE, price) + return True + + +def getCurrentPrice(): + return Get(GetContext(), CURRENT_PRICE) + + +def cancelRequest(requestId, payment, callBackFunc, expiration): + assert (CheckWitness(OWNER)) + assert (ChainlinkClientCall('cancelChainlinkRequest', [OWNER, requestId, payment, callBackFunc, expiration])) + return True + + +def concatKey(str1, str2): + return concat(concat(str1, '_'), str2) + + +def DynamicCallFunction(callAddress, callbackFunctionId, params): + res = DynamicAppCall(callAddress, callbackFunctionId, params) + if res and res == b'\x01': + return True + else: + return False + + +def DynamicCallFunctionResult(callAddress, callbackFunctionId, params): + return DynamicAppCall(callAddress, callbackFunctionId, params) \ No newline at end of file diff --git a/ont-contracts/demo/test.py b/ont-contracts/demo/test.py new file mode 100644 index 0000000..63a91c3 --- /dev/null +++ b/ont-contracts/demo/test.py @@ -0,0 +1,61 @@ +from ontology.builtins import concat +from ontology.interop.Ontology.Runtime import Base58ToAddress +from ontology.interop.System.App import RegisterAppCall, DynamicAppCall +from ontology.interop.System.ExecutionEngine import GetExecutingScriptHash +from ontology.interop.System.Runtime import CheckWitness, Notify +from ontology.interop.System.Storage import GetContext, Put, Get +from ontology.libont import bytearray_reverse + +CURRENT_PRICE = 'CurrentPrice' + +OWNER = Base58ToAddress('AbG3ZgFrMK6fqwXWR1WkQ1d1EYVunCwknu') + +ChainlinkCall = RegisterAppCall('ed6bb0abbe24e5603a7f2a5c44e056f3eaeb5949', 'operation', 'args') +ChainlinkClientCall = RegisterAppCall('fb11d3b30a54ae147e86f57d9e554578f68a0041', 'operation', 'args') +Link = RegisterAppCall('bfb52e4b8a5b49099e1ac0ef55789053f2ea347d', 'operation', 'args') +OracleCall = RegisterAppCall('04dc7f8a0ff88de0784ef742650a1d79495565ae', 'operation', 'args') +CBORCall = RegisterAppCall('3f75e2814021abed8a616da8d408d1347cac988f', 'operation', 'args') + +ContractAddress = GetExecutingScriptHash() + + +def Main(operation, args): + if operation == 'requestEthereumPrice': + assert (len(args) == 3) + oracle = args[0] + jobId = args[1] + payment = args[2] + return requestEthereumPrice(oracle, jobId, payment) + + return False + + +def requestEthereumPrice(oracle, jobId, payment): + # assert (CheckWitness(OWNER)) + req = ChainlinkClientCall('buildChainlinkRequest', [jobId, ContractAddress, 'fullfill']) + req = ChainlinkCall('add', [req, "url", "https://etherprice.com/api"]) + req = ChainlinkCall('addStringArray', [req, "path", ["recent", "usd"]]) + # Notify([OWNER, oracle, req, payment]) + assert (ChainlinkClientCall('sendChainlinkRequestTo', [OWNER, oracle, req, payment])) + return [OWNER, oracle, req, payment] + + +def addStringArray(request, key, values): + request = CBORCall('encodeString', [request, key]) + request = CBORCall('startArray', request) + for value in range(values): + request = CBORCall('encodeString', [request, value]) + request = CBORCall('endSequence', request) + return request + + +def DynamicCallFunction(callAddress, callbackFunctionId, params): + res = DynamicAppCall(callAddress, callbackFunctionId, params) + if res and res == b'\x01': + return True + else: + return False + + +def DynamicCallFunctionResult(callAddress, callbackFunctionId, params): + return DynamicAppCall(callAddress, callbackFunctionId, params) \ No newline at end of file diff --git a/ont-contracts/erc677.py b/ont-contracts/erc677.py new file mode 100644 index 0000000..3bef89d --- /dev/null +++ b/ont-contracts/erc677.py @@ -0,0 +1,308 @@ +OntCversion = '2.0.0' +""" +An Example of OEP-4 +""" +from ontology.builtins import concat +from ontology.interop.Ontology.Runtime import Base58ToAddress +from ontology.interop.System.Action import RegisterAction +from ontology.interop.System.App import DynamicAppCall +from ontology.interop.System.ExecutionEngine import GetExecutingScriptHash +from ontology.interop.System.Runtime import Notify, CheckWitness +from ontology.interop.System.Storage import GetContext, Get, Put, Delete +from ontology.libont import bytearray_reverse + +# a83e612e126b467ec805968f9c1ce147617187e5 +TransferEvent = RegisterAction("transfer", "from", "to", "amount") +ApprovalEvent = RegisterAction("approval", "owner", "spender", "amount") +TransferAndCallEvent = RegisterAction("transferAndCallEvent", "from", "to", "amount", "data") + +ContractAddress = GetExecutingScriptHash() + +ctx = GetContext() + +NAME = 'MyToken' +SYMBOL = 'MYT' +DECIMALS = 18 +FACTOR = 100000000000000000 +OWNER = Base58ToAddress("AbG3ZgFrMK6fqwXWR1WkQ1d1EYVunCwknu") +# OWNER = bytearray(b'\x61\x6f\x2a\x4a\x38\x39\x6f\xf2\x03\xea\x01\xe6\xc0\x70\xae\x42\x1b\xb8\xce\x2d') +TOTAL_AMOUNT = 1000000000 +BALANCE_PREFIX = bytearray(b'\x01') +APPROVE_PREFIX = b'\x02' +SUPPLY_KEY = 'TotalSupply' + + +def Main(operation, args): + """ + :param operation:a + :param args: + :return: + """ + # 'init' has to be invokded first after deploying the contract to store the necessary and important info in the blockchain + if operation == 'init': + return init() + if operation == 'name': + return name() + if operation == 'symbol': + return symbol() + if operation == 'decimals': + return decimals() + if operation == 'totalSupply': + return totalSupply() + if operation == 'balanceOf': + if len(args) != 1: + return False + acct = args[0] + return balanceOf(acct) + if operation == 'transfer': + if len(args) != 3: + return False + else: + 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': + if len(args) != 4: + return False + 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': + if len(args) != 3: + return False + owner = args[0] + spender = args[1] + amount = args[2] + return approve(owner, spender, amount) + if operation == 'allowance': + if len(args) != 2: + return False + owner = args[0] + spender = args[1] + return allowance(owner, spender) + + if operation == 'transferAndCall': + if len(args) != 4: + return False + from_acct = args[0] + to_acct = args[1] + amount = args[2] + data = args[3] + return transferAndCall(from_acct, to_acct, amount, data) + return False + + +def init(): + """ + initialize the contract, put some important info into the storage in the blockchain + :return: + """ + + if len(OWNER) != 20: + Notify(["Owner illegal!"]) + return False + if Get(ctx, SUPPLY_KEY): + Notify("Already initialized!") + return False + else: + total = TOTAL_AMOUNT * FACTOR + Put(ctx, SUPPLY_KEY, total) + Put(ctx, concat(BALANCE_PREFIX, OWNER), total) + + # Notify(["transfer", "", Base58ToAddress(OWNER), total]) + # ownerBase58 = AddressToBase58(OWNER) + TransferEvent("", OWNER, total) + + 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 + + +def totalSupply(): + """ + :return: the total supply of the token + """ + return Get(ctx, SUPPLY_KEY) + + +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)) + + +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: + return False + + fromKey = concat(BALANCE_PREFIX,from_acct) + fromBalance = Get(ctx,fromKey) + if amount > fromBalance: + return False + if amount == fromBalance: + Delete(ctx,fromKey) + else: + Put(ctx,fromKey,fromBalance - amount) + + toKey = concat(BALANCE_PREFIX,to_acct) + toBalance = Get(ctx,toKey) + Put(ctx,toKey,toBalance + amount) + + # Notify(["transfer", AddressToBase58(from_acct), AddressToBase58(to_acct), amount]) + # TransferEvent(AddressToBase58(from_acct), AddressToBase58(to_acct), amount) + TransferEvent(from_acct, to_acct, amount) + + return True + +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: + # return False is wrong since the previous transaction will be successful + raise Exception("transferMulti failed.") + return True + + +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: + return False + if amount > balanceOf(owner) or amount < 0: + return False + + key = concat(concat(APPROVE_PREFIX, owner), spender) + Put(ctx, key, amount) + + # Notify(["approval", AddressToBase58(owner), AddressToBase58(spender), amount]) + # ApprovalEvent(AddressToBase58(owner), AddressToBase58(spender), amount) + ApprovalEvent(owner, spender, amount) + + return True + + +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: + return False + + fromKey = concat(BALANCE_PREFIX, from_acct) + fromBalance = Get(ctx, fromKey) + if amount > fromBalance or amount < 0: + return False + + approveKey = concat(concat(APPROVE_PREFIX, from_acct), spender) + approvedAmount = Get(ctx, approveKey) + toKey = concat(BALANCE_PREFIX, to_acct) + + if amount > approvedAmount: + return False + elif amount == approvedAmount: + Delete(ctx, approveKey) + Put(ctx, fromKey, fromBalance - amount) + else: + Put(ctx, approveKey, approvedAmount - amount) + Put(ctx, fromKey, fromBalance - amount) + + toBalance = Get(ctx, toKey) + Put(ctx, toKey, toBalance + amount) + + # Notify(["transfer", AddressToBase58(from_acct), AddressToBase58(to_acct), amount]) + # TransferEvent(AddressToBase58(from_acct), AddressToBase58(to_acct), amount) + TransferEvent(from_acct, to_acct, amount) + + 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) + + +########################## extend OEP4 ################################ +def transferAndCall(from_acct, to_acct, amount, data): + assert (transfer(from_acct, bytearray_reverse(to_acct), amount)) + TransferAndCallEvent(from_acct, to_acct, amount, data) + assert (contractFallback(from_acct, to_acct, amount, data)) + return True + + +def contractFallback(from_acct, to_acct, amount, data): + assert (callBackFunction(bytearray_reverse(to_acct), "onTokenTransfer", from_acct, amount, data)) + return True + + +def callBackFunction(callbackAddress, callbackFunctionId, from_acct, amount, data): + params = [from_acct, amount, data] + res = DynamicAppCall(callbackAddress, callbackFunctionId, params) + if res and res == b'\x01': + return True + else: + return False \ No newline at end of file diff --git a/ont-contracts/lib/CBOR.py b/ont-contracts/lib/CBOR.py new file mode 100644 index 0000000..4e163e4 --- /dev/null +++ b/ont-contracts/lib/CBOR.py @@ -0,0 +1,121 @@ +############## COBR Library############ +# ec8a6c55b34a6d3488db02742ad199eda4fd72c1 +from ontology.interop.System.App import RegisterAppCall + +MAJOR_TYPE_INT = 0 +MAJOR_TYPE_NEGATIVE_INT = 1 +MAJOR_TYPE_BYTES = 2 +MAJOR_TYPE_STRING = 3 +MAJOR_TYPE_ARRAY = 4 +MAJOR_TYPE_MAP = 5 +MAJOR_TYPE_CONTENT_FREE = 7 + +BufferCall = RegisterAppCall('d5f12664535717af51f52fe2aa88a18d327ea1b9', 'operation', 'args') + +def Main(operation, args): + if operation == 'encodeType': + assert (len(args) == 3) + buf = args[0] + major = args[1] + value = args[2] + return encodeType(buf, major, value) + + if operation == 'encodeIndefiniteLengthType': + assert (len(args) == 2) + buf = args[0] + major = args[1] + return encodeIndefiniteLengthType(buf, major) + + if operation == 'encodeUInt': + assert (len(args) == 2) + buf = args[0] + value = args[1] + return encodeUInt(buf, value) + + if operation == 'encodeInt': + assert (len(args) == 2) + buf = args[0] + value = args[1] + return encodeInt(buf, value) + + if operation == 'encodeBytes': + assert (len(args) == 2) + buf = args[0] + value = args[1] + return encodeBytes(buf, value) + + if operation == 'encodeString': + assert (len(args) == 2) + buf = args[0] + value = args[1] + return encodeString(buf, value) + + if operation == 'startArray': + assert (len(args) == 1) + buf = args[0] + return startArray(buf) + + if operation == 'startMap': + assert (len(args) == 1) + buf = args[0] + return startMap(buf) + + if operation == 'endSequence': + assert (len(args) == 1) + buf = args[0] + return endSequence(buf) + return True + +def encodeType(buf, major, value): + if value <= 23: + buf = BufferCall('WriteUint8', [(major << 5) | value, buf]) + elif value <= 0xFF: + buf = BufferCall('WriteUint8', [(major << 5) | 24, buf]) + buf = BufferCall('WriteUint8', [value, buf]) + elif value <= 0xFFFF: + buf = BufferCall('WriteUint8', [(major << 5) | 25, buf]) + buf = BufferCall('WriteUint16', [value, buf]) + elif value <= 0xFFFFFFFF: + buf = BufferCall('WriteUint8', [(major << 5) | 26, buf]) + buf = BufferCall('WriteUint32', [value, buf]) + elif value <= 0xFFFFFFFFFFFFFFFF: + buf = BufferCall('WriteUint8', [(major << 5) | 27, buf]) + buf = BufferCall('WriteUint64', [value, buf]) + return buf + + +def encodeIndefiniteLengthType(buf, major): + return BufferCall('WriteUint8', [(major << 5 | 31), buf]) + + +def encodeUInt(buf, value): + return encodeType(buf, MAJOR_TYPE_INT, value) + + +def encodeInt(buf, value): + if value >= 0: + return encodeType(buf, MAJOR_TYPE_INT, value) + else: + return encodeType(buf, MAJOR_TYPE_NEGATIVE_INT, -1 - value) + + +def encodeBytes(buf, value): + buf = encodeType(buf, MAJOR_TYPE_BYTES, len(value)) + return BufferCall('WriteBytes', [value, buf]) + + +def encodeString(buf, value): + buf = encodeType(buf, MAJOR_TYPE_STRING, len(value)) + return BufferCall('WriteString', [value, buf]) + + +def startArray(buf): + return encodeIndefiniteLengthType(buf, MAJOR_TYPE_ARRAY) + + +def startMap(buf): + return encodeIndefiniteLengthType(buf, MAJOR_TYPE_MAP) + + +def endSequence(buf): + return encodeIndefiniteLengthType(buf, MAJOR_TYPE_CONTENT_FREE) \ No newline at end of file diff --git a/ont-contracts/lib/ZeroCopySink.py b/ont-contracts/lib/ZeroCopySink.py new file mode 100644 index 0000000..efdc5c5 --- /dev/null +++ b/ont-contracts/lib/ZeroCopySink.py @@ -0,0 +1,154 @@ +OntCversion = '2.0.0' + +''' +Zero Copy Source contract to help serialize data or struct to a series of bytes + +Started: Nov 26th, 2019 +Author: Yinghao Liu +''' + +from ontology.interop.System.Runtime import Notify +from ontology.libont import str +from ontology.builtins import concat + +''' +If the returned offset is -1, means the offset is out of the buff's range +''' + +def Main(operation, args): + if operation == "WriteBool": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteBool(v, buff) + if operation == "WriteByte": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteByte(v, buff) + if operation == "WriteUint8": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteUint8(v, buff) + if operation == "WriteUint16": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteUint16(v, buff) + if operation == "WriteUint32": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteUint32(v, buff) + if operation == "WriteUint64": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteUint64(v, buff) + if operation == "WriteUint255": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteUint255(v, buff) + if operation == "WriteVarBytes": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteVarBytes(v, buff) + if operation == "WriteBytes20": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteBytes20(v, buff) + if operation == "WriteBytes32": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteBytes32(v, buff) + if operation == "WriteString": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteString(v, buff) + if operation == "WriteBytes": + assert (len(args) == 2) + v = args[0] + buff = args[1] + return WriteBytes(v, buff) + return False + + +def WriteBool(v, buff): + if v == True: + buff = concat(buff, b'\x01') + elif v == False: + buff = concat(buff, b'\x00') + else: + assert (False) + return buff + +def WriteByte(v, buff): + assert (len(v) == 1) + vBs = v[0:1] + buff = concat(buff, vBs) + return buff + + +def WriteUint8(v, buff): + assert (v >= 0 and v <= 0xFF) + buff = concat(buff, _convertNumToBytes(v, 1)) + return buff + + +def WriteUint16(v, buff): + assert (v >= 0 and v <= 0xFFFF) + buff = concat(buff, _convertNumToBytes(v, 2)) + return buff + + +def WriteUint32(v, buff): + assert (v >= 0 and v <= 0xFFFFFFFF) + buff = concat(buff, _convertNumToBytes(v, 4)) + return buff + + +def WriteUint64(v, buff): + assert (v >= 0 and v <= 0xFFFFFFFFFFFFFFFF) + buff = concat(buff, _convertNumToBytes(v, 8)) + return buff + + +def WriteUint255(v, buff): + assert (v >= 0 and v <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + return WriteBytes(_convertNumToBytes(v, 32), buff) + + +def WriteBytes(v, buff): + return concat(buff, v) + + +def WriteVarBytes(v, buff): + return WriteBytes(v, buff) + +def WriteBytes20(v, buff): + assert (len(v) == 20) + return WriteBytes(v, buff) + +def WriteBytes32(v, buff): + assert (len(v) == 32) + return WriteBytes(v, buff) + +def WriteString(v, buff): + return WriteVarBytes(v, buff) + + +def _convertNumToBytes(_val, bytesLen): + l = len(_val) + if l < bytesLen: + for i in range(bytesLen - l): + _val = concat(_val, b'\x00') + if l > bytesLen: + _val = _val[:bytesLen] + return _val + diff --git a/ont-contracts/lib/ZeroCopySource.py b/ont-contracts/lib/ZeroCopySource.py new file mode 100644 index 0000000..4f529fc --- /dev/null +++ b/ont-contracts/lib/ZeroCopySource.py @@ -0,0 +1,202 @@ +OntCversion = '2.0.0' + +''' +Zero Copy Source contract to help deserialize data or struct from a series of bytes + +Started: Nov 26th, 2019 +Author: Yinghao Liu +''' + +from ontology.interop.System.Runtime import Notify +from ontology.libont import str +from ontology.builtins import concat + +''' +If the returned offset is -1, means the offset is out of the buff's range +''' + + +def Main(operation, args): + if operation == "NextBool": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextBool(buff, offset) + if operation == "NextByte": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextByte(buff, offset) + if operation == "NextUint8": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextUint8(buff, offset) + if operation == "NextUint16": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextUint16(buff, offset) + if operation == "NextUint32": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextUint32(buff, offset) + if operation == "NextUint64": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextUint64(buff, offset) + if operation == "NextUint255": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextUint255(buff, offset) + if operation == "NextBytes": + assert (len(args) == 3) + buff = args[0] + offset = args[1] + count = args[2] + return NextBytes(buff, offset, count) + if operation == "NextVarUint": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextVarUint(buff, offset) + if operation == "NextVarBytes": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextVarBytes(buff, offset) + if operation == "NextBytes20": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextBytes20(buff, offset) + if operation == "NextBytes32": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextBytes32(buff, offset) + if operation == "NextString": + assert (len(args) == 2) + buff = args[0] + offset = args[1] + return NextString(buff, offset) + return False + + +def NextBool(buff, offset): + if offset + 1 > len(buff): + return [False, -1] + val = buff[offset:offset + 1] + if val == 1: + return [True, offset + 1] + elif val == 0: + return [False, offset + 1] + assert (False) + + +def NextByte(buff, offset): + if offset + 1 > len(buff): + return [0, -1] + return [buff[offset:offset + 1], offset + 1] + + +def NextUint8(buff, offset): + if offset + 1 > len(buff): + return [0, -1] + return [_convertBytesToNum(buff[offset:offset + 1]), offset + 1] + + +def NextUint16(buff, offset): + if offset + 2 > len(buff): + return [0, -1] + return [_convertBytesToNum(buff[offset:offset + 2]), offset + 2] + + +def NextUint32(buff, offset): + if offset + 4 > len(buff): + return [0, -1] + return [_convertBytesToNum(buff[offset:offset + 4]), offset + 4] + + +def NextUint64(buff, offset): + if offset + 8 > len(buff): + return [0, -1] + res = _convertBytesToNum(buff[offset:offset + 8]) + return [res, offset + 8] + + +def NextUint255(buff, offset): + if offset + 32 > len(buff): + return [0, -1] + # TODO, limit the converted bytes has the maximum length of 32 + return [_convertBytesToNum(buff[offset:offset + 32]), offset + 32] + + +def NextBytes(buff, offset, count): + if offset + count > len(buff): + return [0, -1] + return [buff[offset:offset + count], offset + count] + + +def NextVarUint(buff, offset): + res = NextByte(buff, offset) + fb = res[0] + offset = res[1] + assert (res[1] > 0) + # we can also use if concat(fb, b'\x00') == 0xfd: + if fb == b'\xfd': + return NextUint16(buff, offset) + elif fb == b'\xfe': + return NextUint32(buff, offset) + elif fb == b'\xff': + return NextUint64(buff, offset) + else: + return [fb, offset] + + +def NextVarBytes(buff, offset): + res = NextVarUint(buff, offset) + return NextBytes(buff, res[1], res[0]) + + +def NextBytes20(buff, offset): + if offset + 20 > len(buff): + return [0, -1] + return [buff[offset:offset + 20], offset + 20] + +def NextBytes32(buff, offset): + if offset + 32 > len(buff): + return [0, -1] + return [buff[offset:offset + 32], offset + 32] + +def NextString(buff, offset): + return NextVarBytes(buff, offset) + + + + +def _convertBytesToNum(_bs): + firstNonZeroPostFromR2L = _getFirstNonZeroPosFromR2L(_bs) + assert (firstNonZeroPostFromR2L >= 0) + Notify(["111", _bs, firstNonZeroPostFromR2L]) + if firstNonZeroPostFromR2L > len(_bs): + return concat(_bs, b'\x00') + else: + return _bs[:firstNonZeroPostFromR2L] + + +def _getFirstNonZeroPosFromR2L(_bs): + bytesLen = len(_bs) + for i in range(bytesLen): + byteI = _bs[bytesLen - i - 1:bytesLen - i] + if byteI != b'\x00': + # convert byte to int + byteI = concat(byteI, b'\x00') + if byteI >= 0x80: + return bytesLen + 1 - i + else: + return bytesLen - i + return -1 \ No newline at end of file diff --git a/ont-contracts/lib/cbor_test.py b/ont-contracts/lib/cbor_test.py new file mode 100644 index 0000000..e69de29 diff --git a/ont-contracts/lib/sink_source_test.py b/ont-contracts/lib/sink_source_test.py new file mode 100644 index 0000000..be440a3 --- /dev/null +++ b/ont-contracts/lib/sink_source_test.py @@ -0,0 +1,121 @@ +OntCversion = '2.0.0' + +''' +Zero Copy Source and Sink testing contract to help verify the function logic + +Started: Nov 26th, 2019 +Author: Yinghao Liu +''' + +from ontology.interop.System.Runtime import Notify +from ontology.interop.System.Storage import Put, GetContext, Get, Delete +from ontology.builtins import concat +from ontology.interop.System.App import RegisterAppCall, DynamicAppCall +from ontology.libont import bytearray_reverse + +def Main(operation, args): + if operation == "setSinkSourceHash": + sinkHash = args[0] + sourceHash = args[1] + return setSinkSourceHash(sinkHash, sourceHash) + if operation == "testSink": + Notify([args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]]) + return testSink(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]) + if operation == "testSource": + return testSource(args[0]) + if operation == "testEqual": + return testEqual(args[0]) + return False + +def setSinkSourceHash(sinkHash, sourceHash): + Put(GetContext(), "SINK", bytearray_reverse(sinkHash)) + Put(GetContext(), "SOURCE", bytearray_reverse(sourceHash)) + Notify(["set", sinkHash, sourceHash]) + return True + +def testSink(bl, u8, u16, u32, u64, b, bs, vbs, addr, hash1, str1): + sinkHash = Get(GetContext(), "SINK") + Notify(["sinkHash", sinkHash]) + buff = None + buff = DynamicAppCall(sinkHash, "WriteBool", [bl, buff]) + Notify([1, buff]) + + buff = DynamicAppCall(sinkHash, "WriteUint8", [u8, buff]) + Notify([2, buff]) + + buff = DynamicAppCall(sinkHash, "WriteUint16", [u16, buff]) + Notify([3, buff, 0xFFFF, 0xFFFF>0]) + + buff = DynamicAppCall(sinkHash, "WriteUint32", [u32, buff]) + Notify([4, buff]) + + + buff = DynamicAppCall(sinkHash, "WriteUint64", [u64, buff]) + Notify([5, buff, u64, 18446744073709551615]) + + buff = DynamicAppCall(sinkHash, "WriteByte", [b, buff]) + Notify([6, buff]) + + buff = DynamicAppCall(sinkHash, "WriteBytes", [bs, buff]) + Notify([7, buff]) + + buff = DynamicAppCall(sinkHash, "WriteVarBytes", [vbs, buff]) + Notify([8, buff]) + + buff = DynamicAppCall(sinkHash, "WriteBytes20", [addr, buff]) + Notify([9, buff]) + + buff = DynamicAppCall(sinkHash, "WriteBytes32", [hash1, buff]) + Notify([10, buff]) + + buff = DynamicAppCall(sinkHash, "WriteString", [str1, buff]) + Notify([11, buff]) + + return buff + + + +def testEqual(v): + v1V = concat(v[0:1], b'\x00') + Notify(["222",v1V, v1V>0x80, v1V == 0x80]) + Notify(["333", v1V > b'\x80\x00', v1V == b'\x80\x00']) + Notify(["444", v[0:1] == b'\xfe', concat(v[0:1], b'\x00') == 0xfe]) + +def testSource(buff): + sourceHash = Get(GetContext(), "SOURCE") + Notify(["sourceHash", sourceHash]) + offset = 0 + res = DynamicAppCall(sourceHash, "NextBool", [buff, offset]) + Notify([1, res, False]) + + res = DynamicAppCall(sourceHash, "NextUint8", [buff, res[1]]) + Notify([2, res, 255]) + + res = DynamicAppCall(sourceHash, "NextUint16", [buff, res[1]]) + Notify([3, res, 65535]) + + res = DynamicAppCall(sourceHash, "NextUint32", [buff, res[1]]) + Notify([4, res, 4294967295]) + + res = DynamicAppCall(sourceHash, "NextUint64", [buff, res[1]]) + Notify([5, res, 10100]) + + res = DynamicAppCall(sourceHash, "NextByte", [buff, res[1]]) + Notify([6, res, 200]) + + res = DynamicAppCall(sourceHash, "NextBytes", [buff, res[1], 6]) + Notify([7, res, "hahaha"]) + + res = DynamicAppCall(sourceHash, "NextVarBytes", [buff, res[1]]) + Notify([8, res, "heiheihei"]) + + res = DynamicAppCall(sourceHash, "NextBytes20", [buff, res[1]]) + Notify([9, res]) + + res = DynamicAppCall(sourceHash, "NextBytes32", [buff, res[1]]) + Notify([10, res]) + + res = DynamicAppCall(sourceHash, "NextString", [buff, res[1]]) + Notify([11, res]) + + return buff diff --git a/ont-contracts/oracle.py b/ont-contracts/oracle.py new file mode 100644 index 0000000..9977825 --- /dev/null +++ b/ont-contracts/oracle.py @@ -0,0 +1,278 @@ +from ontology.builtins import concat, sha256 +from ontology.interop.Ontology.Runtime import Base58ToAddress +from ontology.interop.System.Action import RegisterAction +from ontology.interop.System.App import DynamicAppCall +from ontology.interop.System.ExecutionEngine import GetExecutingScriptHash, GetCallingScriptHash +from ontology.interop.System.Runtime import CheckWitness, GetTime, Notify, Serialize +from ontology.interop.System.Storage import GetContext, Get, Put, Delete +from ontology.libont import bytearray_reverse + +# cd6fb4678612081f272052e949ef2309f181e91b +EXPIRY_TIME = 30 + +INITIALIZED = "INIT" +MINIMUM_CONSUMER_GAS_LIMIT = 100000000 +OWNER = Base58ToAddress("AbG3ZgFrMK6fqwXWR1WkQ1d1EYVunCwknu") +LINKTOKEN_ADDRESS = 'linktokenAddress' +COMMITMENTS_PRIFX = 'commitments' +AUTHORIZE_NODES_PREFIX = 'authorizeNodes' +WITHDRAWABLETOKENS = 'withdrawableTokens' + +ContractAddress = GetExecutingScriptHash() + +CancelOracleRequestEvent = RegisterAction("cancelOracleRequest", "requestId") + +OracleRequestEvent = RegisterAction("oracleRequest", "specId", "sender", "requestId", "payment", "callbackAddress", + "callbackFunctionId", "expiration", "dataVersion", "data", "callFunctionId") + + +def Main(operation, args): + if operation == 'init': + assert (len(args) == 1) + link = args[0] + return init(link) + + if operation == 'oracleRequest': + assert (len(args) == 9) + spender = args[0] + payment = args[1] + specId = args[2] + callbackAddress = args[3] + callbackFunctionId = args[4] + nonce = args[5] + dataVersion = args[6] + data = args[7] + callFunctionId = args[8] + return oracleRequest(spender, payment, specId, callbackAddress, callbackFunctionId, nonce, dataVersion, + data, callFunctionId) + + if operation == 'fulfillOracleRequest': + assert (len(args) == 7) + node = args[0] + requestId = args[1] + payment = args[2] + callbackAddress = args[3] + callbackFunctionId = args[4] + expiration = args[5] + data = args[6] + return fulfillOracleRequest(node, requestId, payment, callbackAddress, callbackFunctionId, expiration, data) + + if operation == 'getAuthorizationStatus': + assert (len(args) == 1) + node = args[0] + return getAuthorizationStatus(node) + + if operation == 'setFulfillmentPermission': + assert (len(args) == 2) + node = args[0] + allowed = args[1] + return setFulfillmentPermission(node, allowed) + + if operation == 'withdraw': + assert (len(args) == 2) + recipient = args[0] + amount = args[1] + return withdraw(recipient, amount) + + if operation == 'withdrawable': + return withdrawable() + + if operation == 'cancelOracleRequest': + assert (len(args) == 6) + sender = args[0] + requestId = args[1] + payment = args[2] + callbackAddress = args[3] + callbackFunctionId = args[4] + expiration = args[5] + return cancelOracleRequest(sender, requestId, payment, callbackAddress, callbackFunctionId, expiration) + + if operation == 'onTokenTransfer': + assert (len(args) == 3) + spender = args[0] + amount = args[1] + data = args[2] + return onTokenTransfer(spender, amount, data) + + if operation == 'getChainLinkToken': + return getChainLinkToken() + return True + + +def init(link): + RequireWitness(OWNER) + # inited = Get(GetContext(), INITIALIZED) + # if inited: + # Notify(["idiot admin, you have initialized the contract"]) + # return False + # else: + Put(GetContext(), INITIALIZED, 1) + Put(GetContext(), LINKTOKEN_ADDRESS, link) + Notify(["Initialized contract successfully!!!!!!!!"]) + return True + + +def oracleRequest(spender, payment, specId, callbackAddress, callbackFunctionId, nonce, dataVersion, data, + callFunctionId): + onlyLINK() + RequireWitness(spender) + payment = payment + 0 + # TODO + requestId = sha256(Serialize([callbackAddress, spender, nonce])) + assert (not Get(GetContext(), concatKey(COMMITMENTS_PRIFX, requestId))) + expiration = GetTime() + EXPIRY_TIME + Put(GetContext(), concatKey(COMMITMENTS_PRIFX, requestId), + Serialize([payment, callbackAddress, callbackFunctionId, expiration])) + OracleRequestEvent(specId, spender, requestId, payment, callbackAddress, callbackFunctionId, expiration, + dataVersion, data, callFunctionId) + return True + + +def fulfillOracleRequest(node, requestId, payment, callbackAddress, callbackFunctionId, expiration, data): + RequireWitness(node) + onlyAuthorizedNode(node) + expiration = expiration + 0 + payment = payment + 0 + paramsHash = Serialize([payment, callbackAddress, callbackFunctionId, expiration]) + assert (Get(GetContext(), concatKey(COMMITMENTS_PRIFX, requestId)) == paramsHash) + + Put(GetContext(), WITHDRAWABLETOKENS, Get(GetContext(), WITHDRAWABLETOKENS) + payment) + Delete(GetContext(), concatKey(COMMITMENTS_PRIFX, requestId)) + assert (callBackFunction(callbackAddress, callbackFunctionId, requestId, data)) + return True + + +def getAuthorizationStatus(node): + return Get(GetContext(), concatKey(AUTHORIZE_NODES_PREFIX, node)) + + +def setFulfillmentPermission(node, allowed): + RequireWitness(OWNER) + Put(GetContext(), concatKey(AUTHORIZE_NODES_PREFIX, node), allowed) + return True + + +# 取回合约中存储的LINK TOKEN +def withdraw(recipient, amount): + RequireWitness(OWNER) + hasAvailableFunds(amount) + assert (_transferLinkFromContact(Get(GetContext(), LINKTOKEN_ADDRESS), recipient, amount)) + return True + + +# 返回合约中可取回的LINK TOKEN +def withdrawable(): + return Get(GetContext(), WITHDRAWABLETOKENS) + + +# 取消oracle服务请求 +def cancelOracleRequest(sender, requestId, payment, callbackAddress, callbackFunctionId, expiration): + RequireWitness(sender) + expiration = expiration + 0 + payment = payment + 0 + + paramsHash = Serialize([payment, callbackAddress, callbackFunctionId, expiration]) + + originParamsHash = Get(GetContext(), concatKey(COMMITMENTS_PRIFX, requestId)) + assert (paramsHash == originParamsHash) + # Notify([originParamsHash, paramsHash, sender, requestId, payment, callbackAddress, callbackFunctionId, expiration]) + assert (expiration <= GetTime()) + Delete(GetContext(), concatKey(COMMITMENTS_PRIFX, requestId)) + + assert (_transferLinkFromContact(bytearray_reverse(getChainLinkToken()), callbackAddress, payment)) + return True + + +# 获取LINK地址 +def getChainLinkToken(): + return Get(GetContext(), LINKTOKEN_ADDRESS) + + +def hasAvailableFunds(amount): + availableFunds = Get(GetContext(), WITHDRAWABLETOKENS) + assert (availableFunds > 0 and availableFunds >= amount) + return True + + +def isValidRequest(requestId): + assert (Get(GetContext(), concatKey(COMMITMENTS_PRIFX, requestId))) + return True + + +def onlyAuthorizedNode(node): + assert (Get(GetContext(), concatKey(AUTHORIZE_NODES_PREFIX, node)) or node == OWNER) + return True + + +def onlyLINK(): + assert (bytearray_reverse(GetCallingScriptHash()) == getChainLinkToken()) + return True + + +def onTokenTransfer(spender, amount, data): + onlyLINK() + # validRequestLength(data) + permittedFunctionsForLINK(data) + data[0] = spender + data[1] = amount + assert (oracleRequest(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8])) + return True + + +def permittedFunctionsForLINK(data): + assert ("oracleRequest" == data[8]) + return True + + +def validRequestLength(data): + assert (len(data) >= 8) + return True + + +def RequireScriptHash(key): + assert (len(key) == 20) + return True + + +def RequireWitness(witness): + assert (CheckWitness(witness)) + return True + + +def concatKey(str1, str2): + return concat(concat(str1, '_'), str2) + + +def _transferLinkFromContact(link, toAcct, amount): + params = [ContractAddress, toAcct, amount] + res = DynamicAppCall(link, 'transfer', params) + if res and res == b'\x01': + return True + else: + return False + + +def _transferLink(link, fromAcct, toAcct, amount): + """ + transfer _transferOEP4 + :param fromacct: + :param toacct: + :param amount: + :return: + """ + RequireWitness(fromAcct) + params = [fromAcct, toAcct, amount] + res = DynamicAppCall(link, 'transfer', params) + if res and res == b'\x01': + return True + else: + return False + + +def callBackFunction(callbackAddress, callbackFunctionId, requestId, data): + params = [requestId, data] + res = DynamicAppCall(callbackAddress, callbackFunctionId, params) + if res and res == b'\x01': + return True + else: + return False \ No newline at end of file