diff --git a/blockchain/testdata/tezos/block_operations_with_internal_calls.json b/blockchain/testdata/tezos/block_operations_with_internal_calls.json new file mode 100644 index 00000000..c73ba718 --- /dev/null +++ b/blockchain/testdata/tezos/block_operations_with_internal_calls.json @@ -0,0 +1,602 @@ +[ + [], + [], + [], + [ + { + "protocol": "PtEdo2ZkT9oKpimTah6x2embF25oss54njMuPzkJTEi5RqfdZFA", + "chain_id": "NetXnuwTfg9Box8", + "hash": "oodhhKgjJPAaktvrByGWoNyTPEFvvMtEzVfbdVav5jJP7szJSiC", + "branch": "BLCww5q5XV1ZixzDGEDBvncYF6nzgHgGS1yjbQ8T7yMzFV1ZSrZ", + "contents": [ + { + "kind": "transaction", + "source": "tz1evBmfWZoPDN38avoRGbjJaLBJUP8AZz6a", + "fee": "200000", + "counter": "11", + "gas_limit": "1040000", + "storage_limit": "1000", + "amount": "0", + "destination": "KT1Vz7PE3QEtw8F4UF3SnJFb97sgD9fZxSdr", + "parameters": { + "entrypoint": "default", + "value": { + "prim": "Right", + "args": [ + { + "prim": "Left", + "args": [ + { + "prim": "Pair", + "args": [ + { + "string": "KT1Vz7PE3QEtw8F4UF3SnJFb97sgD9fZxSdr%set_value" + }, + { + "prim": "Pair", + "args": [ + { + "int": "5" + }, + { + "prim": "Pair", + "args": [ + { + "string": "jobid" + }, + { + "prim": "Pair", + "args": [ + { + "string": "KT1TuXZYMD9tf5Bbv99cpZMB9sy1kbcZJKTk" + }, + { + "prim": "Pair", + "args": [ + [ + { + "prim": "Elt", + "args": [ + { + "string": "from" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "EUR" + } + ] + } + ] + } + ] + }, + { + "prim": "Elt", + "args": [ + { + "string": "to" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "XTZ" + } + ] + } + ] + } + ] + } + ], + { + "int": "3" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + }, + "metadata": { + "balance_updates": [ + { + "kind": "contract", + "contract": "tz1evBmfWZoPDN38avoRGbjJaLBJUP8AZz6a", + "change": "-200000" + }, + { + "kind": "freezer", + "category": "fees", + "delegate": "tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU", + "cycle": 37, + "change": "200000" + } + ], + "operation_result": { + "status": "applied", + "storage": [ + { + "prim": "Pair", + "args": [ + { + "bytes": "0000d3789f9f4223c271403bde738fd304691e796ee8" + }, + { + "prim": "None" + } + ] + }, + { + "int": "2" + }, + { + "int": "8" + }, + { + "bytes": "019e5225d7e53fce0fe227ced8286460e91f78cd3e00" + } + ], + "big_map_diff": [ + { + "action": "update", + "big_map": "8", + "key_hash": "expru2dKqDfZG8hu4wNGkiyunvq2hdSKuVYtcKta7BWP6Q18oNxKjS", + "key": { + "int": "1" + }, + "value": { + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" + } + } + ], + "balance_updates": [ + { + "kind": "contract", + "contract": "tz1evBmfWZoPDN38avoRGbjJaLBJUP8AZz6a", + "change": "-23000" + } + ], + "consumed_gas": "31295", + "consumed_milligas": "31294423", + "storage_size": "2029", + "paid_storage_size_diff": "92", + "lazy_storage_diff": [ + { + "kind": "big_map", + "id": "8", + "diff": { + "action": "update", + "updates": [ + { + "key_hash": "expru2dKqDfZG8hu4wNGkiyunvq2hdSKuVYtcKta7BWP6Q18oNxKjS", + "key": { + "int": "1" + }, + "value": { + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" + } + } + ] + } + } + ] + }, + "internal_operation_results": [ + { + "kind": "transaction", + "source": "KT1Vz7PE3QEtw8F4UF3SnJFb97sgD9fZxSdr", + "nonce": 0, + "amount": "0", + "destination": "KT1P1tjogGsCrGBYX2VdG31bbxrhQ1a7YBfY", + "parameters": { + "entrypoint": "transfer_and_call", + "value": { + "prim": "Pair", + "args": [ + { + "int": "3" + }, + { + "prim": "Pair", + "args": [ + { + "bytes": "0507070a0000001f01eac4c08634d9ea12e95736216df49b366e6f39ab007365745f76616c7565070700af9891880c07070a0000001601eac4c08634d9ea12e95736216df49b366e6f39ab0007070002070701000000056a6f62696407070a0000001601d3f69a73c4ccba87b23885bb2487591f21ab355c00020000002c0704010000000466726f6d05080508010000000345555207040100000002746f05080508010000000358545a" + }, + { + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" + } + ] + } + ] + } + }, + "result": { + "status": "applied", + "storage": [ + { + "prim": "Pair", + "args": [ + { + "bytes": "0000d3789f9f4223c271403bde738fd304691e796ee8" + }, + { + "prim": "Pair", + "args": [ + { + "int": "0" + }, + { + "int": "0" + } + ] + } + ] + }, + { + "prim": "Pair", + "args": [ + { + "int": "1" + }, + { + "int": "2" + } + ] + }, + { + "prim": "False" + }, + { + "int": "3" + } + ], + "big_map_diff": [ + { + "action": "update", + "big_map": "0", + "key_hash": "exprthJ86cgnpvx2iAQWREmkv7fnXYsAQt3pkLjFQaSbAJgVJHAyF3", + "key": { + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" + }, + "value": { + "int": "3" + } + }, + { + "action": "update", + "big_map": "0", + "key_hash": "expru2kJjcACHrSuvM84uXoCyttb4ZkpgxRBfXnpxjK66Q7uW2ugPf", + "key": { + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab00" + }, + "value": { + "int": "499997" + } + } + ], + "balance_updates": [ + { + "kind": "contract", + "contract": "tz1evBmfWZoPDN38avoRGbjJaLBJUP8AZz6a", + "change": "-16750" + } + ], + "consumed_gas": "38860", + "consumed_milligas": "38859602", + "storage_size": "4704", + "paid_storage_size_diff": "67", + "lazy_storage_diff": [ + { + "kind": "big_map", + "id": "3", + "diff": { + "action": "update", + "updates": [] + } + }, + { + "kind": "big_map", + "id": "2", + "diff": { + "action": "update", + "updates": [] + } + }, + { + "kind": "big_map", + "id": "1", + "diff": { + "action": "update", + "updates": [] + } + }, + { + "kind": "big_map", + "id": "0", + "diff": { + "action": "update", + "updates": [ + { + "key_hash": "expru2kJjcACHrSuvM84uXoCyttb4ZkpgxRBfXnpxjK66Q7uW2ugPf", + "key": { + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab00" + }, + "value": { + "int": "499997" + } + }, + { + "key_hash": "exprthJ86cgnpvx2iAQWREmkv7fnXYsAQt3pkLjFQaSbAJgVJHAyF3", + "key": { + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" + }, + "value": { + "int": "3" + } + } + ] + } + } + ] + } + }, + { + "kind": "transaction", + "source": "KT1P1tjogGsCrGBYX2VdG31bbxrhQ1a7YBfY", + "nonce": 1, + "amount": "0", + "destination": "KT1TuXZYMD9tf5Bbv99cpZMB9sy1kbcZJKTk", + "parameters": { + "entrypoint": "on_token_transfer", + "value": { + "prim": "Pair", + "args": [ + { + "int": "3" + }, + { + "prim": "Pair", + "args": [ + { + "bytes": "0507070a0000001f01eac4c08634d9ea12e95736216df49b366e6f39ab007365745f76616c7565070700af9891880c07070a0000001601eac4c08634d9ea12e95736216df49b366e6f39ab0007070002070701000000056a6f62696407070a0000001601d3f69a73c4ccba87b23885bb2487591f21ab355c00020000002c0704010000000466726f6d05080508010000000345555207040100000002746f05080508010000000358545a" + }, + { + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab00" + } + ] + } + ] + } + }, + "result": { + "status": "applied", + "storage": { + "prim": "Pair", + "args": [ + [ + { + "prim": "Pair", + "args": [ + { + "prim": "True" + }, + { + "bytes": "0000d3789f9f4223c271403bde738fd304691e796ee8" + } + ] + }, + { + "int": "5" + }, + { + "int": "1" + }, + { + "bytes": "019e5225d7e53fce0fe227ced8286460e91f78cd3e00" + } + ], + { + "int": "6" + } + ] + }, + "big_map_diff": [ + { + "action": "update", + "big_map": "6", + "key_hash": "exprvLkgZCFy8PFS4jNq3JnH7p4ucZtyrmyS4dCzNRXo6h1PsBekhc", + "key": { + "bytes": "f6ab2c039df8049391a4c09effa97861d8de2400aea24c6d1e55961e389fd726" + }, + "value": [ + { + "int": "3" + }, + { + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab007365745f76616c7565" + }, + { + "int": "1619142191" + }, + { + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab00" + }, + { + "int": "2" + }, + { + "string": "mock" + }, + { + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" + }, + [ + { + "prim": "Elt", + "args": [ + { + "string": "from" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "EUR" + } + ] + } + ] + } + ] + }, + { + "prim": "Elt", + "args": [ + { + "string": "to" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "XTZ" + } + ] + } + ] + } + ] + } + ] + ] + } + ], + "balance_updates": [ + { + "kind": "contract", + "contract": "tz1evBmfWZoPDN38avoRGbjJaLBJUP8AZz6a", + "change": "-57250" + } + ], + "consumed_gas": "18604", + "consumed_milligas": "18603107", + "storage_size": "3462", + "paid_storage_size_diff": "229", + "lazy_storage_diff": [ + { + "kind": "big_map", + "id": "6", + "diff": { + "action": "update", + "updates": [ + { + "key_hash": "exprvLkgZCFy8PFS4jNq3JnH7p4ucZtyrmyS4dCzNRXo6h1PsBekhc", + "key": { + "bytes": "f6ab2c039df8049391a4c09effa97861d8de2400aea24c6d1e55961e389fd726" + }, + "value": [ + { + "int": "3" + }, + { + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab007365745f76616c7565" + }, + { + "int": "1619142191" + }, + { + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab00" + }, + { + "int": "2" + }, + { + "string": "jobid" + }, + { + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" + }, + [ + { + "prim": "Elt", + "args": [ + { + "string": "from" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "EUR" + } + ] + } + ] + } + ] + }, + { + "prim": "Elt", + "args": [ + { + "string": "to" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "XTZ" + } + ] + } + ] + } + ] + } + ] + ] + } + ] + } + } + ] + } + } + ] + } + } + ], + "signature": "sigTNg2bE4qzTYwbXgJbJAh7jsF3GEFGUFg2ZugSLpBWc9kJxy9QJrWNeig44nNUbMcYCmAsGb9P6GBi2xJhtFyf5QxXSuyW" + } + ] +] diff --git a/blockchain/testdata/tezos_test_block_operations_user_initiated.json b/blockchain/testdata/tezos/block_operations_without_internal_calls.json similarity index 100% rename from blockchain/testdata/tezos_test_block_operations_user_initiated.json rename to blockchain/testdata/tezos/block_operations_without_internal_calls.json diff --git a/blockchain/testdata/tezos/contract_script_normalized.json b/blockchain/testdata/tezos/contract_script_normalized.json new file mode 100644 index 00000000..34d6ae99 --- /dev/null +++ b/blockchain/testdata/tezos/contract_script_normalized.json @@ -0,0 +1,200 @@ +{ + "code": [ + { + "prim": "parameter", + "args": [] + }, + { + "prim": "storage", + "args": [ + { + "prim": "pair", + "args": [ + { + "prim": "pair", + "args": [ + { + "prim": "pair", + "args": [ + { + "prim": "bool", + "annots": [ + "%active" + ] + }, + { + "prim": "address", + "annots": [ + "%adminAddress" + ] + } + ] + }, + { + "prim": "int", + "annots": [ + "%minCancelTimeout" + ] + }, + { + "prim": "nat", + "annots": [ + "%minPayment" + ] + }, + { + "prim": "address", + "annots": [ + "%tokenAddress" + ] + } + ], + "annots": [ + "%config" + ] + }, + { + "prim": "big_map", + "args": [ + { + "prim": "bytes" + }, + { + "prim": "pair", + "args": [ + { + "prim": "nat", + "annots": [ + "%payment" + ] + }, + { + "prim": "pair", + "args": [ + { + "prim": "address", + "annots": [ + "%callbackAddress" + ] + }, + { + "prim": "timestamp", + "annots": [ + "%cancelTimeout" + ] + }, + { + "prim": "address", + "annots": [ + "%clientAddress" + ] + }, + { + "prim": "nat", + "annots": [ + "%id" + ] + }, + { + "prim": "string", + "annots": [ + "%jobId" + ] + }, + { + "prim": "address", + "annots": [ + "%oracleAddress" + ] + }, + { + "prim": "map", + "args": [ + { + "prim": "string" + }, + { + "prim": "or", + "args": [ + { + "prim": "bytes", + "annots": [ + "%bytes" + ] + }, + { + "prim": "or", + "args": [ + { + "prim": "int", + "annots": [ + "%int" + ] + }, + { + "prim": "string", + "annots": [ + "%string" + ] + } + ] + } + ] + } + ], + "annots": [ + "%parameters" + ] + } + ], + "annots": [ + "%request" + ] + } + ] + } + ], + "annots": [ + "%requests" + ] + } + ] + } + ] + }, + { + "prim": "code", + "args": [] + } + ], + "storage": { + "prim": "Pair", + "args": [ + [ + { + "prim": "Pair", + "args": [ + { + "prim": "True" + }, + { + "bytes": "0000d3789f9f4223c271403bde738fd304691e796ee8" + } + ] + }, + { + "int": "5" + }, + { + "int": "1" + }, + { + "bytes": "019e5225d7e53fce0fe227ced8286460e91f78cd3e00" + } + ], + { + "int": "6" + } + ] + } +} diff --git a/blockchain/testdata/tezos/invalid_map_value.json b/blockchain/testdata/tezos/invalid_map_value.json new file mode 100644 index 00000000..e04ac813 --- /dev/null +++ b/blockchain/testdata/tezos/invalid_map_value.json @@ -0,0 +1,44 @@ +[ + { + "prim": "Pair", + "args": [ + { + "string": "from" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "EUR" + } + ] + } + ] + } + ] + }, + { + "prim": "Pair", + "args": [ + { + "string": "to" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "XTZ" + } + ] + } + ] + } + ] + } +] diff --git a/blockchain/testdata/tezos/map_value.json b/blockchain/testdata/tezos/map_value.json new file mode 100644 index 00000000..226aff58 --- /dev/null +++ b/blockchain/testdata/tezos/map_value.json @@ -0,0 +1,44 @@ +[ + { + "prim": "Elt", + "args": [ + { + "string": "from" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "EUR" + } + ] + } + ] + } + ] + }, + { + "prim": "Elt", + "args": [ + { + "string": "to" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "XTZ" + } + ] + } + ] + } + ] + } +] diff --git a/blockchain/testdata/tezos/request_type_normalized.json b/blockchain/testdata/tezos/request_type_normalized.json new file mode 100644 index 00000000..4f9ff352 --- /dev/null +++ b/blockchain/testdata/tezos/request_type_normalized.json @@ -0,0 +1,94 @@ +{ + "prim": "pair", + "args": [ + { + "prim": "nat", + "annots": [ + "%payment" + ] + }, + { + "prim": "pair", + "args": [ + { + "prim": "address", + "annots": [ + "%callbackAddress" + ] + }, + { + "prim": "timestamp", + "annots": [ + "%cancelTimeout" + ] + }, + { + "prim": "address", + "annots": [ + "%clientAddress" + ] + }, + { + "prim": "nat", + "annots": [ + "%id" + ] + }, + { + "prim": "string", + "annots": [ + "%jobId" + ] + }, + { + "prim": "address", + "annots": [ + "%oracleAddress" + ] + }, + { + "prim": "map", + "args": [ + { + "prim": "string" + }, + { + "prim": "or", + "args": [ + { + "prim": "bytes", + "annots": [ + "%bytes" + ] + }, + { + "prim": "or", + "args": [ + { + "prim": "int", + "annots": [ + "%int" + ] + }, + { + "prim": "string", + "annots": [ + "%string" + ] + } + ] + } + ] + } + ], + "annots": [ + "%parameters" + ] + } + ], + "annots": [ + "%request" + ] + } + ] +} diff --git a/blockchain/testdata/tezos/requests_type_normalized.json b/blockchain/testdata/tezos/requests_type_normalized.json new file mode 100644 index 00000000..1c2dc257 --- /dev/null +++ b/blockchain/testdata/tezos/requests_type_normalized.json @@ -0,0 +1,105 @@ +{ + "prim": "big_map", + "args": [ + { + "prim": "bytes" + }, + { + "prim": "pair", + "args": [ + { + "prim": "nat", + "annots": [ + "%payment" + ] + }, + { + "prim": "pair", + "args": [ + { + "prim": "address", + "annots": [ + "%callbackAddress" + ] + }, + { + "prim": "timestamp", + "annots": [ + "%cancelTimeout" + ] + }, + { + "prim": "address", + "annots": [ + "%clientAddress" + ] + }, + { + "prim": "nat", + "annots": [ + "%id" + ] + }, + { + "prim": "string", + "annots": [ + "%jobId" + ] + }, + { + "prim": "address", + "annots": [ + "%oracleAddress" + ] + }, + { + "prim": "map", + "args": [ + { + "prim": "string" + }, + { + "prim": "or", + "args": [ + { + "prim": "bytes", + "annots": [ + "%bytes" + ] + }, + { + "prim": "or", + "args": [ + { + "prim": "int", + "annots": [ + "%int" + ] + }, + { + "prim": "string", + "annots": [ + "%string" + ] + } + ] + } + ] + } + ], + "annots": [ + "%parameters" + ] + } + ], + "annots": [ + "%request" + ] + } + ] + } + ], + "annots": [ + "%requests" + ] +} diff --git a/blockchain/testdata/tezos_test_block_operations_sc_initiated.json b/blockchain/testdata/tezos_test_block_operations_sc_initiated.json deleted file mode 100644 index c1bd5160..00000000 --- a/blockchain/testdata/tezos_test_block_operations_sc_initiated.json +++ /dev/null @@ -1,692 +0,0 @@ -[ - [], - [], - [], - [ - { - "protocol": "nonsense", - "chainID": "main", - "hash": "8BADF00D8BADF00D8BADF00D8BADF00D8BADF00D8BADF00D8BADF00D", - "branch": "8BADF00D", - "contents": [ - { - "kind": "transaction", - "source": "tz1Q8dtxfG6AbAnYtaFLwcw6WPYCCntbf2Jk", - "fee": "100000", - "counter": "1469017", - "gas_limit": "750000", - "storage_limit": "1000", - "amount": "0", - "destination": "KT1EfcYSSmqcLthMH1uwYn1mDjML1gsX2SP2", - "parameters": { - "entrypoint": "default", - "value": { - "prim": "Right", - "args": [ - { - "prim": "Left", - "args": [ - { - "prim": "Unit" - } - ] - } - ] - } - }, - "metadata": { - "balance_updates": [ - { - "kind": "contract", - "contract": "tz1Q8dtxfG6AbAnYtaFLwcw6WPYCCntbf2Jk", - "change": "-100000" - }, - { - "kind": "freezer", - "category": "fees", - "delegate": "tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU", - "cycle": 277, - "change": "100000" - } - ], - "operation_result": { - "status": "applied", - "storage": { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "bytes": "00007539e349d38f3762a7f7ce381e6e36a986090696" - }, - { - "prim": "Pair", - "args": [ - { - "int": "2" - }, - { - "bytes": "019b1c03bed5e07b153bbff1bf216f5d621a0767d500" - } - ] - } - ] - }, - { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "bytes": "012439cf083ae5f0f5c0a133a7fdf49976ce2432c300" - }, - { - "prim": "Some", - "args": [ - { - "int": "1" - } - ] - } - ] - }, - { - "prim": "Pair", - "args": [ - { - "int": "9" - }, - { - "bytes": "test123" - } - ] - } - ] - } - ] - }, - "balance_updates": [ - { - "kind": "contract", - "contract": "tz1Q8dtxfG6AbAnYtaFLwcw6WPYCCntbf2Jk", - "change": "-2000" - } - ], - "consumed_gas": "224595", - "storage_size": "2439", - "paid_storage_size_diff": "2" - }, - "internal_operation_results": [ - { - "kind": "transaction", - "source": "KT1EfcYSSmqcLthMH1uwYn1mDjML1gsX2SP2", - "nonce": 0, - "amount": "0", - "destination": "KT1BtK9HryqaQ5kXzhsJvDfUZfcpwsdFpMQN", - "parameters": { - "entrypoint": "proxy", - "value": { - "prim": "Pair", - "args": [ - { - "bytes": "019b1c03bed5e07b153bbff1bf216f5d621a0767d500" - }, - { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "int": "2" - }, - { - "prim": "Pair", - "args": [ - { - "int": "1" - }, - { - "bytes": "0001" - } - ] - } - ] - }, - { - "prim": "Pair", - "args": [ - [ - { - "prim": "Elt", - "args": [ - { - "string": "from" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Right", - "args": [ - { - "string": "XTZ" - } - ] - } - ] - } - ] - }, - { - "prim": "Elt", - "args": [ - { - "string": "multiply" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Left", - "args": [ - { - "int": "10000" - } - ] - } - ] - } - ] - }, - { - "prim": "Elt", - "args": [ - { - "string": "to" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Right", - "args": [ - { - "string": "USD" - } - ] - } - ] - } - ] - } - ], - { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae007365745f78747a757364" - }, - { - "int": "1594815460" - } - ] - } - ] - } - ] - } - ] - } - }, - "result": { - "status": "applied", - "storage": { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "bytes": "000065c1192ac41c582a0492b1f2ca1cc9fee790a7f1" - }, - { - "prim": "Pair", - "args": [ - { - "int": "1" - }, - { - "int": "10059" - } - ] - } - ] - }, - { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "prim": "Unit" - }, - { - "int": "10060" - } - ] - }, - { - "prim": "Pair", - "args": [ - { - "prim": "False" - }, - { - "int": "10061" - } - ] - } - ] - } - ] - }, - "big_map_diff": [ - { - "action": "update", - "big_map": "10059", - "key_hash": "exprvHAVWporVuKLxDEgnkLqcGy7sMZdRU7oJ7Wbr3YsKXLbbAATRN", - "key": { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae00" - }, - { - "int": "0" - } - ] - }, - "value": { - "int": "998" - } - }, - { - "action": "update", - "big_map": "10059", - "key_hash": "exprthGoc6NKnqtNfpHzudy3qKLTxLMTKzAaXagiqn1qL2KYNQQztR", - "key": { - "prim": "Pair", - "args": [ - { - "bytes": "019b1c03bed5e07b153bbff1bf216f5d621a0767d500" - }, - { - "int": "0" - } - ] - }, - "value": { - "int": "2" - } - } - ], - "balance_updates": [ - { - "kind": "contract", - "contract": "tz1Q8dtxfG6AbAnYtaFLwcw6WPYCCntbf2Jk", - "change": "-67000" - } - ], - "consumed_gas": "258137", - "storage_size": "8903", - "paid_storage_size_diff": "67" - } - }, - { - "kind": "transaction", - "source": "KT1BtK9HryqaQ5kXzhsJvDfUZfcpwsdFpMQN", - "nonce": 1, - "amount": "0", - "destination": "KT1Address", - "parameters": { - "entrypoint": "create_request", - "value": { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae00" - }, - { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "int": "2" - }, - { - "prim": "Pair", - "args": [ - { - "int": "1" - }, - { - "bytes": "test123" - } - ] - } - ] - }, - { - "prim": "Pair", - "args": [ - [ - { - "prim": "Elt", - "args": [ - { - "string": "from" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Right", - "args": [ - { - "string": "XTZ" - } - ] - } - ] - } - ] - }, - { - "prim": "Elt", - "args": [ - { - "string": "multiply" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Left", - "args": [ - { - "int": "10000" - } - ] - } - ] - } - ] - }, - { - "prim": "Elt", - "args": [ - { - "string": "to" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Right", - "args": [ - { - "string": "USD" - } - ] - } - ] - } - ] - } - ], - { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae007365745f78747a757364" - }, - { - "int": "1594815460" - } - ] - } - ] - } - ] - } - ] - } - }, - "result": { - "status": "applied", - "storage": { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "prim": "True" - }, - { - "bytes": "00001257c4edc38abcdd5039e7313fcb87e0db42c743" - } - ] - }, - { - "prim": "Pair", - "args": [ - { - "int": "0" - }, - { - "int": "0" - } - ] - } - ] - }, - { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "int": "1" - }, - { - "int": "10065" - } - ] - }, - { - "prim": "Pair", - "args": [ - { - "int": "10066" - }, - { - "bytes": "012439cf083ae5f0f5c0a133a7fdf49976ce2432c300" - } - ] - } - ] - } - ] - }, - "big_map_diff": [ - { - "action": "update", - "big_map": "10066", - "key_hash": "exprua5vA8enk8gpXUGU85Q7BU5TGEk89VYcgspJtuKjHdkiFPMWZ9", - "key": { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae00" - }, - { - "int": "1" - } - ] - }, - "value": { - "int": "9" - } - }, - { - "action": "update", - "big_map": "10065", - "key_hash": "exprtZBwZUeYYYfUs9B9Rg2ywHezVHnCCnmF9WsDQVrs582dSK63dC", - "key": { - "int": "0" - }, - "value": { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "int": "2" - }, - { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae00" - }, - { - "int": "1" - } - ] - } - ] - }, - { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "bytes": "0001" - }, - [ - { - "prim": "Elt", - "args": [ - { - "string": "from" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Right", - "args": [ - { - "string": "XTZ" - } - ] - } - ] - } - ] - }, - { - "prim": "Elt", - "args": [ - { - "string": "multiply" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Left", - "args": [ - { - "int": "10000" - } - ] - } - ] - } - ] - }, - { - "prim": "Elt", - "args": [ - { - "string": "to" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Right", - "args": [ - { - "string": "USD" - } - ] - } - ] - } - ] - } - ] - ] - }, - { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae007365745f78747a757364" - }, - { - "int": "1594815460" - } - ] - } - ] - } - ] - } - } - ], - "balance_updates": [ - { - "kind": "contract", - "contract": "tz1Q8dtxfG6AbAnYtaFLwcw6WPYCCntbf2Jk", - "change": "-297000" - } - ], - "consumed_gas": "92331", - "storage_size": "4359", - "paid_storage_size_diff": "297" - } - } - ] - } - } - ] - } - ] -] diff --git a/blockchain/tezos.go b/blockchain/tezos.go index 32bbc4ed..765dc2c7 100644 --- a/blockchain/tezos.go +++ b/blockchain/tezos.go @@ -2,12 +2,14 @@ package blockchain import ( "bufio" + "bytes" "encoding/json" "errors" "fmt" "io" "io/ioutil" "net/http" + "strconv" "strings" "time" @@ -118,7 +120,7 @@ func (tzs tezosSubscription) readMessages() { return } - events, err := extractEventsFromBlock(blockJSON, tzs.addresses, tzs.jobid) + events, err := tzs.extractEventsFromBlock(blockJSON, tzs.addresses, tzs.jobid) if err != nil { logger.Error(err) return @@ -183,6 +185,172 @@ func (tzs tezosSubscription) getBlock(blockID string) ([]byte, error) { return body, nil } +type xtzHeader struct { + Hash string `json:"hash"` + Level int `json:"level"` + Proto int `json:"proto"` + Predecessor string `json:"predecessor"` + Timestamp string `json:"timestamp"` + ValidationPass int `json:"validation_pass"` + OperationsHash string `json:"operations_hash"` + Fitness []string `json:"fitness"` + Context string `json:"context"` + ProtocolData string `json:"protocol_data"` +} + +type xtzTransaction struct { + Protocol string `json:"protocol"` + ChainID string `json:"chain_id"` + Hash string `json:"hash"` + Branch string `json:"branch"` + Contents []xtzTransactionContent `json:"contents"` +} + +type xtzTransactionContent struct { + Kind string `json:"kind"` + Source string `json:"source"` + Fee string `json:"fee"` + Counter string `json:"counter"` + GasLimit string `json:"gas_limit"` + StorageLimit string `json:"storage_limit"` + Amount string `json:"amount"` + Destination string `json:"destination"` + Parameters interface{} `json:"parameters"` + Metadata xtzTransactionContentMetadata `json:"metadata"` +} + +type xtzTransactionContentMetadata struct { + BalanceUpdates []interface{} `json:"balance_updates"` + OperationResult xtzOperationResult `json:"operation_result"` + InternalOperationResults []xtzInternalOperationResult `json:"internal_operation_results"` +} + +type xtzInternalOperationResult struct { + Kind string `json:"kind"` + Source string `json:"source"` + Nonce int `json:"nonce"` + Amount string `json:"amount"` + Destination string `json:"destination"` + Parameters xtzInternalOperationParameters `json:"parameters"` + Result xtzOperationResult `json:"result"` +} + +type xtzOperationResult struct { + Status string `json:"status"` + Errors []interface{} `json:"errors"` + Storage json.RawMessage `json:"storage"` + BalanceUpdates []interface{} `json:"balance_updates"` + ConsumedGas string `json:"consumed_gas"` + StorageSize string `json:"storage_size"` + PaidStorageSizeDiff string `json:"paid_storage_size_diff"` + BigMapDiff []xtzBigMapDiff `json:"big_map_diff"` +} + +type xtzBigMapDiff struct { + Action string `json:"action"` + BigMap string `json:"big_map"` + KeyHash string `json:"key_hash"` + // (key, value) can be a JSON array ([]) or a JSON object ({}) + Key michelsonExpression `json:"key"` + Value json.RawMessage `json:"value"` +} + +type xtzInternalOperationParameters struct { + Entrypoint string `json:"entrypoint"` + Value json.RawMessage `json:"value"` +} + +const ( + requestFieldID string = "id" + requestFieldJobID string = "jobId" + requestFieldOracleAddress string = "oracleAddress" + requestFieldParameters string = "parameters" +) + +// Core data types (https://tezos.gitlab.io/alpha/michelson.html#core-data-types-and-notations) +const ( + primTypeInt = "int" // "prim":"int" + primTypeNat = "nat" // "prim":"nat" + primTypeTimestamp = "timestamp" // "prim":"timestamp" + primTypeAddress = "address" // "prim":"address" + primTypeString = "string" // "prim":"string" + primTypeBytes = "bytes" // "prim":"bytes" + primTypeBool = "bool" // "prim":"bool" + primTypePair = "pair" // "prim":"pair" + primTypeMap = "map" // "prim":"map" + primTypeBigMap = "big_map" // "prim":"big_map" +) + +// https://tezos.gitlab.io/alpha/michelson.html#full-grammar +const ( + primValuePair = "Pair" // "prim":"Pair" + primValueElt = "Elt" // "prim":"Elt" + primValueLeft = "Left" // "prim":"Left" + primValueRight = "Right" // "prim":"Right" + primValueTrue = "True" // "prim":"True" + primValueFalse = "False" // "prim":"False" +) + +type michelsonExpression struct { + Bytes string `json:"bytes,omitempty"` + Int string `json:"int,omitempty"` + String string `json:"string,omitempty"` + // Generic prim (can have args and annots) + Prim string `json:"prim,omitempty"` + // Args cannot be of type []michelsonExpression, + // since it can contain arrays and objects as items + Args []json.RawMessage `json:"args,omitempty"` + Annots []string `json:"annots,omitempty"` +} + +// Get contract storage type and value +func (tzs tezosSubscription) getContractStorage(contractAddress string) (michelsonExpression, json.RawMessage, error) { + var typeExpr michelsonExpression + var valueRaw json.RawMessage + + // Get contract script in "Optimized" form + postBody, _ := json.Marshal(map[string]string{ + "unparsing_mode": "Optimized", + }) + resp, err := http.Post( + fmt.Sprintf("%s/chains/main/blocks/head/context/contracts/%s/script/normalized", tzs.endpoint, contractAddress), + "application/json", + bytes.NewBuffer(postBody), + ) + if err != nil { + return typeExpr, valueRaw, err + } + defer logger.ErrorIfCalling(resp.Body.Close) + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return typeExpr, valueRaw, err + } + + // Get the storage type + // { "code": [{...}, {"prim": "storage", args [{}]}, {...}], "storage": {} } + // The line below gets this item --------------^ + result := gjson.GetBytes(body, `code.#(prim=="storage").args.0`) + var raw []byte + if result.Index > 0 { + raw = body[result.Index : result.Index+len(result.Raw)] + } else { + raw = []byte(result.Raw) + } + typeExpr, err = unmarshalExpression(raw) + if err != nil { + return typeExpr, valueRaw, err + } + + // Get the storage value + // { "code": [], "storage": {} } + // -------------------------^ + result = gjson.GetBytes(body, "storage") + valueRaw = json.RawMessage(result.Raw) + + return typeExpr, valueRaw, nil +} + func (tzs tezosSubscription) Unsubscribe() { logger.Info("Unsubscribing from Tezos endpoint", tzs.endpoint) tzs.isDone = true @@ -191,7 +359,7 @@ func (tzs tezosSubscription) Unsubscribe() { } } -func extractEventsFromBlock(data []byte, addresses []string, jobID string) ([]subscriber.Event, error) { +func (tzs tezosSubscription) extractEventsFromBlock(data []byte, addresses []string, jobID string) ([]subscriber.Event, error) { if !gjson.ValidBytes(data) { return nil, errors.New("got invalid JSON object from Tezos RPC endpoint") } @@ -222,13 +390,14 @@ func extractEventsFromBlock(data []byte, addresses []string, jobID string) ([]su for _, t := range transactions { /* There is no official documentation on this, but according to Alex Eichhorn: - > there is a concept of batch transactions (not officially called that way) where a list of multiple operations is signed at once by the same key + > there is a concept of operation batch where a list of multiple operations is signed at once by the same key > wallets use it to issue reveal+transaction or reveal+origination etc, bakers use it for batch payouts > AFAIK the list cannot be empty - Note that this list only contains user-initiated calls. - SC-initiated calls are buried deep inside the call to that SC, as a callback (return value of that SC). - You can find this under metadata->internal_operation_results + Note that this list only contains user-initiated calls. (implicit account (tz1) calls) + + User initiated transactions can originate "internal calls" that + can be found under metadata->internal_operation_results */ for _, content := range t.Contents { // Check to see if this is a successful oracle request to @@ -238,35 +407,56 @@ func extractEventsFromBlock(data []byte, addresses []string, jobID string) ([]su continue } - var args xtzArgs - err := json.Unmarshal(op.Parameters.Value, &args) + // Get the request type from the oracle storage type, it contains + // the label(called annotation) and the type of each value + typeExpr, valueRaw, err := tzs.getContractStorage(op.Destination) if err != nil { return nil, err } - vals, err := args.GetValues() + // Extract "%requests" big_map ID to allow custom storage structs as long as mandatory annotations + // are present in the type expression. (%requests, %id, %jobID, %parameters) + bigMapID, err := extractBigMapID(typeExpr, valueRaw) if err != nil { return nil, err } - // Check if our jobID matches - if !matchesXtzJobid(vals, jobID) { - continue + // Get big_map_diff associated with the big_map ID abtained above + bigMapDiff, err := getBigMapDiff(bigMapID, op.Result.BigMapDiff) + if err != nil { + return nil, err + } + + // Get the big_map type expression + bigMapTypeExpr, err := getRequestType(typeExpr) + if err != nil { + return nil, err } - params, err := getXtzKeyValues(vals) + // Extract the new request from the big_map_diff + values, err := extractValuesFromExpression(bigMapTypeExpr, bigMapDiff.Value) if err != nil { return nil, err } - // Set the address to the oracle address. - // The adapter will use this to fulfill the request. - params["address"] = op.Destination - params["request_id"], err = op.Result.GetRequestId() + + if !matchesJobID(jobID, values[requestFieldJobID].(string)) { + continue + } + + // extract request id from the big_map_diff + requestId, err := extractVariantValue(bigMapDiff.Key) if err != nil { return nil, err } - event, err := json.Marshal(params) + // Add request arguments (a clean map with only the mandatory arguments) + requestArguments := make(map[string]interface{}) + requestArguments[requestFieldParameters] = values[requestFieldParameters] + requestArguments[requestFieldJobID] = values[requestFieldJobID] + requestArguments[requestFieldOracleAddress] = op.Destination + requestArguments[requestFieldID] = requestId + + event, err := json.Marshal(requestArguments) if err != nil { return nil, err } @@ -276,16 +466,6 @@ func extractEventsFromBlock(data []byte, addresses []string, jobID string) ([]su return events, nil } -func matchesXtzJobid(values []string, expected string) bool { - if len(values) < 4 { - // Not enough params - return false - } - - jobID := values[3] - return matchesJobID(expected, jobID) -} - func getSuccessfulRequestCall(content xtzTransactionContent, oracleAddresses []string) (xtzInternalOperationResult, bool) { if content.Metadata.OperationResult.Status != "applied" { // Transaction did not succeed @@ -293,8 +473,9 @@ func getSuccessfulRequestCall(content xtzTransactionContent, oracleAddresses []s } for _, op := range content.Metadata.InternalOperationResults { - // Check for oracle request - if op.Parameters.Entrypoint != "create_request" { + // Check if any transaction called the expected entry-points + ep := op.Parameters.Entrypoint + if ep != "create_request" && ep != "on_token_transfer" { continue } @@ -329,199 +510,259 @@ func extractBlockIDFromHeaderJSON(data []byte) (string, error) { return header.Hash, nil } -type xtzHeader struct { - Hash string `json:"hash"` - Level int `json:"level"` - Proto int `json:"proto"` - Predecessor string `json:"predecessor"` - Timestamp string `json:"timestamp"` - ValidationPass int `json:"validation_pass"` - OperationsHash string `json:"operations_hash"` - Fitness []string `json:"fitness"` - Context string `json:"context"` - ProtocolData string `json:"protocol_data"` -} +// Extract the request values map(key => value) from the expression, +// where (key) is the annotation obtained from the storage type +func extractValuesFromExpression(typeExpr michelsonExpression, valueRaw json.RawMessage) (map[string]interface{}, error) { + req := make(map[string]interface{}) -type xtzTransaction struct { - Protocol string `json:"protocol"` - ChainID string `json:"chain_id"` - Hash string `json:"hash"` - Branch string `json:"branch"` - Contents []xtzTransactionContent `json:"contents"` -} + valueExpr, err := convertRawToMichelsonExpression(valueRaw) + if err != nil { + // Raw was not a valid michelson expression + return req, err + } -type xtzTransactionContent struct { - Kind string `json:"kind"` - Source string `json:"source"` - Fee string `json:"fee"` - Counter string `json:"counter"` - GasLimit string `json:"gas_limit"` - StorageLimit string `json:"storage_limit"` - Amount string `json:"amount"` - Destination string `json:"destination"` - Parameters interface{} `json:"parameters"` - Metadata xtzTransactionContentMetadata `json:"metadata"` -} + switch typeExpr.Prim { + case primTypeInt, primTypeNat, primTypeTimestamp, primTypeBigMap, + primTypeString, primTypeBytes, primTypeAddress, primTypeBool: + // Extract field name from the annotation + field, err := getAnnotation(typeExpr.Annots) + if err != nil { + return req, err + } + if req[field], err = extractVariantValue(valueExpr); err != nil { + return req, err + } + case primTypeMap: + // Extract field name from the annotation + field, err := getAnnotation(typeExpr.Annots) + if err != nil { + return nil, err + } -type xtzTransactionContentMetadata struct { - BalanceUpdates []interface{} `json:"balance_updates"` - OperationResult xtzOperationResult `json:"operation_result"` - InternalOperationResults []xtzInternalOperationResult `json:"internal_operation_results"` -} + // Extract key and values from the map + keyValues, err := extractMapKeyValues(valueExpr.Args) + if err != nil { + return nil, err + } -type xtzInternalOperationResult struct { - Kind string `json:"kind"` - Source string `json:"source"` - Nonce int `json:"nonce"` - Amount string `json:"amount"` - Destination string `json:"destination"` - Parameters xtzInternalOperationParameters `json:"parameters"` - Result xtzOperationResult `json:"result"` -} + req[field] = keyValues + case primTypePair: + // If valueExpr is more optimized than the typeExpr (then optimize typeExpr) + // In this situations, typeExpr is a pair and not a tuple with more than 2 items + for len(typeExpr.Args) < len(valueExpr.Args) { + innerExpr, err := unmarshalExpression(typeExpr.Args[1]) + if err != nil { + return nil, err + } + typeExpr.Args = append(typeExpr.Args[:1], innerExpr.Args...) + } -type xtzOperationResult struct { - Status string `json:"status"` - Errors []interface{} `json:"errors"` - Storage json.RawMessage `json:"storage"` - BalanceUpdates []interface{} `json:"balance_updates"` - ConsumedGas string `json:"consumed_gas"` - StorageSize string `json:"storage_size"` - PaidStorageSizeDiff string `json:"paid_storage_size_diff"` - BigMapDiff []xtzBigMapDiff `json:"big_map_diff"` -} + for i, reqType := range typeExpr.Args { + // Get type expression + tExpr, err := unmarshalExpression(reqType) + if err != nil { + return nil, err + } -type xtzBigMapDiff struct { - Action string `json:"action"` - BigMap string `json:"big_map"` - KeyHash string `json:"key_hash"` - Key interface{} `json:"key"` - Value xtzValue `json:"value"` -} - -// Since there is no event-based request_id, we need to check -// the storage changes in the operation to see the oracle -// request id. -func (op xtzOperationResult) GetRequestId() (string, error) { - var arg xtzArgs - err := json.Unmarshal(op.Storage, &arg) - if err != nil { - return "", err + keyValues, err := extractValuesFromExpression(tExpr, valueExpr.Args[i]) + if err != nil { + return nil, err + } + for k, v := range keyValues { + req[k] = v + } + } + default: + return req, fmt.Errorf("unexpected expression type %s", typeExpr.Prim) } - vals, err := arg.GetValues() - if err != nil { - return "", err - } + return req, nil +} - // We expect to get the big_map index in the - // 7th value from storage changes. - if len(vals) < 7 { - return "", errors.New("expected at least 7 storage changes") - } +// Extract key values from map +func extractMapKeyValues(vExpr []json.RawMessage) (map[string]interface{}, error) { + // The following types are enforced: + // * [key] => string + // * [value] => (string|int64) + values := make(map[string]interface{}) - bigMapIndex := vals[6] + // For each map entry, get the associated key and value + for _, expr := range vExpr { + expression, err := unmarshalExpression(expr) + if err != nil { + return nil, err + } - for _, bmd := range op.BigMapDiff { - if bmd.BigMap != bigMapIndex { - continue + // "prim":"Elt" means "element" and every map entry is identified by an element + if expression.Prim != primValueElt && len(expression.Args) == 2 { + return values, fmt.Errorf("expected element (Elt) with 2 args, but instead got %v", expression.Prim) + } + + // Get key expression + keyExpr, err := unmarshalExpression(expression.Args[0]) + if err != nil { + return nil, err } - return bmd.Value.Int, nil + + // Get value expression + valueExpr, err := unmarshalExpression(expression.Args[1]) + if err != nil { + return nil, err + } + + // Extract the normalized value from michelson expression + value, err := extractVariantValue(valueExpr) + if err != nil { + return nil, err + } + + values[keyExpr.String] = value } - return "", errors.New("unable to find request_id") + return values, nil } -type xtzInternalOperationParameters struct { - Entrypoint string `json:"entrypoint"` - Value json.RawMessage `json:"value"` -} +// Lookup a given annotation and return the respective expression +func extractExpressionByAnnotation(expr michelsonExpression, annot string) (michelsonExpression, error) { + // INPUTS: + // -- expr: { prim: "pair", annots: ["%a"], args: [{ prim: "int", annots: ["%b"] }, {}] } + // -- annot: "%b" + // + // OUTPUT: { prim: "int", annots: ["%b"] } -func getXtzKeyValues(vals []string) (map[string]string, error) { - if len(vals) < 7 { - return nil, errors.New("not enough values provided") + if len(expr.Annots) > 0 && expr.Annots[0] == annot { + return expr, nil } - // Values #1, #2, #3, #4 are client, amount, (client) request_id and job_id. - // The last two values are target and timeout. - // We ignore these when converting to key-value arrays, - // then we add the necessary values with correct keys. - kv := convertStringArrayToKV(vals[4 : len(vals)-2]) - kv["payment"] = vals[1] - kv["request_id"] = vals[2] - return kv, nil -} + for _, arg := range expr.Args { + exp, err := unmarshalExpression(arg) + if err != nil { + return exp, err + } -type xtzValue struct { - Bytes string `json:"bytes,omitempty"` - Int string `json:"int,omitempty"` - String string `json:"string,omitempty"` -} + if exp, err = extractExpressionByAnnotation(exp, annot); err == nil { + return exp, err + } + } -type xtzArgs struct { - xtzValue - Prim string `json:"prim,omitempty"` - Args []json.RawMessage `json:"args,omitempty"` + return expr, fmt.Errorf("expression doesn't contain the annotation %s", annot) } -func (a xtzArgs) GetValue() string { - if a.Bytes != "" { - return a.Bytes - } else if a.Int != "" { - return a.Int - } else if a.String != "" { - return a.String +// Get value type of big map "requests", the type is used to map values to their respective field names +func getRequestType(typeExpr michelsonExpression) (michelsonExpression, error) { + // Extract requests big_map type from contract storage type + typeExpr, err := extractExpressionByAnnotation(typeExpr, "%requests") + if err != nil { + return typeExpr, err } - return "" + + // Only the "%requests" value type is needed + typeExpr, err = unmarshalExpression(typeExpr.Args[1]) + return typeExpr, err } -func (a xtzArgs) GetValues() ([]string, error) { - // If we don't have any args, return the value - if len(a.Args) < 1 { - return []string{a.GetValue()}, nil +// Get big_map_diff associated with oracle "requests" big_map +func getBigMapDiff(bigMapID string, bigMapDiffs []xtzBigMapDiff) (xtzBigMapDiff, error) { + for _, diff := range bigMapDiffs { + if diff.BigMap == bigMapID && (diff.Action == "alloc" || diff.Action == "update") { + return diff, nil + } } + return xtzBigMapDiff{}, fmt.Errorf("could not find a big_map_diff update associated with big map %s", bigMapID) +} - args, err := a.GetArgs() +// Estract big_map ID, it is used to find big_map diffs +func extractBigMapID(tExpr michelsonExpression, vExpr json.RawMessage) (string, error) { + storageValues, err := extractValuesFromExpression(tExpr, vExpr) if err != nil { - return nil, err + return "", err + } + + oracleRequests := storageValues["requests"] + if oracleRequests == nil { + return "", fmt.Errorf("the contract storage is not compatible, did not find any (requests) annotation") + } + return fmt.Sprintf("%v", oracleRequests), err +} + +// Get field from type annotation (["%field"] => "field") +func getAnnotation(annot []string) (string, error) { + l := len(annot) + if l != 1 { + return "", fmt.Errorf("expected one annotation, but received %d", l) + } + if !strings.HasPrefix(annot[0], "%") { + return "", fmt.Errorf(`invalid annotation format (%s), expected prefix (%c)`, annot[0], '%') } + return strings.TrimPrefix(annot[0], "%"), nil +} - var result []string - for _, arg := range args { - values, err := arg.GetValues() +// Extract value from michelson expression +func extractVariantValue(expr michelsonExpression) (interface{}, error) { + if expr.String != "" { + return expr.String, nil + } else if expr.Int != "" { + // Convert string to int64 + return strconv.ParseInt(expr.Int, 10, 64) + } else if expr.Bytes != "" { + // In hexadecimal + return expr.Bytes, nil + } else if expr.Prim == primValueTrue { + return true, nil + } else if expr.Prim == primValueFalse { + return false, nil + } else if len(expr.Args) > 0 { + expr, err := unmarshalExpression(expr.Args[0]) if err != nil { return nil, err } - result = append(result, values...) + return extractVariantValue(expr) } - return result, nil + return nil, fmt.Errorf("could not get value from variant") } -func (a xtzArgs) GetArgs() ([]xtzArgs, error) { - if len(a.Args) < 1 { - return nil, nil - } - - var args []xtzArgs - // Iterate the array of objects _or_ arrays - for _, bz := range a.Args { - var arg xtzArgs - err := json.Unmarshal(bz, &arg) - // If we cannot unmarshal to an object, check if we can unmarshal to an array of objects +// Convert json.RawMessage to michelsonExpression +// valueRaw can be an object or an array +func convertRawToMichelsonExpression(valueRaw json.RawMessage) (michelsonExpression, error) { + var vExpression michelsonExpression + err := json.Unmarshal(valueRaw, &vExpression) + // If we cannot unmarshal to an object, check if we can unmarshal to an array of objects + // This can happen because of the "unparsing_mode" used in "internal transactions", + // which currenctly is (Optimized mode) + // + // [Readable] : comb pairs are always written Pair(a1, a2, ... an) + // [Optimized] : the shortest representation is used. It depends on the number of arguments: + // n < 4 => { Pair(a1, a2), Pair(a3, Pair(a4, a5)) } + // n >= 4 => Pair(a1, a2, ... an) + // [Optimized_legacy] : nested pairs are always used: { Pair(a1, a2), Pair(a3, Pair(a4, a5)) } + if err != nil { + vExpressions, err := convertToJSONArray(valueRaw) if err != nil { - var argArgs []xtzArgs - err = json.Unmarshal(bz, &argArgs) - if err != nil { - // This was neither an object or an array of objects, return error - return nil, err - } - // Unmarshal succeeded, add the objects to our result list - args = append(args, argArgs...) - continue + // This was neither an object or an array of objects, return error + return vExpression, err + } + + // vExpr is an array of expressions ([ expression, ... ]) + // It must either be a sequence or an optimized pair + vExpression = michelsonExpression{ + Args: vExpressions, } - // Our initial object unmarshal succeeded, add the object to result list - args = append(args, arg) } - return args, nil + return vExpression, nil +} + +// Convert json.RawMessage to []json.RawMessage +func convertToJSONArray(expr json.RawMessage) ([]json.RawMessage, error) { + var expressions []json.RawMessage + err := json.Unmarshal(expr, &expressions) + return expressions, err +} + +// Convert json.RawMessage to michelsonExpression +func unmarshalExpression(expr json.RawMessage) (michelsonExpression, error) { + var expression michelsonExpression + err := json.Unmarshal(expr, &expression) + return expression, err } diff --git a/blockchain/tezos_test.go b/blockchain/tezos_test.go index 95203af9..41874631 100644 --- a/blockchain/tezos_test.go +++ b/blockchain/tezos_test.go @@ -1,9 +1,13 @@ package blockchain import ( + "encoding/json" "io/ioutil" + "net/http" + "net/http/httptest" "os" "path" + "regexp" "testing" "github.com/smartcontractkit/external-initiator/eitest" @@ -14,6 +18,11 @@ import ( "github.com/tidwall/gjson" ) +// Regex of the paths being mocked in tests +var ( + pathContractScript = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/contracts\/[A-z0-9]+\/script/normalized`) +) + func TestCreateTezosSubscriber(t *testing.T) { t.Run("creates tezosSubscriber from subscription", func(t *testing.T) { @@ -69,57 +78,270 @@ func Test_extractBlockIDFromHeaderJSON(t *testing.T) { } func Test_extractEventsFromBlock(t *testing.T) { - addresses := []string{"KT1Address", "KT2Address"} + addresses := []string{"KT1TuXZYMD9tf5Bbv99cpZMB9sy1kbcZJKTk"} wd, _ := os.Getwd() - ui := path.Join(wd, "testdata/tezos_test_block_operations_user_initiated.json") + + ui := path.Join(wd, "testdata/tezos/block_operations_without_internal_calls.json") userInitiatedSampleFile, err := os.Open(ui) require.NoError(t, err) + userInitiatedSampleJSON, err := ioutil.ReadAll(userInitiatedSampleFile) + require.NoError(t, err) defer eitest.MustClose(userInitiatedSampleFile) - sci := path.Join(wd, "testdata/tezos_test_block_operations_sc_initiated.json") + sci := path.Join(wd, "testdata/tezos/block_operations_with_internal_calls.json") scInitiatedSampleFile, err := os.Open(sci) require.NoError(t, err) + scInitiatedSampleJSON, err := ioutil.ReadAll(scInitiatedSampleFile) + require.NoError(t, err) defer eitest.MustClose(scInitiatedSampleFile) - userInitiatedSampleJSON, err := ioutil.ReadAll(userInitiatedSampleFile) + scriptNormalized := path.Join(wd, "testdata/tezos/contract_script_normalized.json") + scriptNormalizedFile, err := os.Open(scriptNormalized) require.NoError(t, err) - scInitiatedSampleJSON, err := ioutil.ReadAll(scInitiatedSampleFile) + scriptNormalizedJSON, err := ioutil.ReadAll(scriptNormalizedFile) require.NoError(t, err) + defer eitest.MustClose(scriptNormalizedFile) + + requestHandlers := []RequestHandler{ + { + RegPath: pathContractScript, + Response: scriptNormalizedJSON, + }, + } + mockServer := httptest.NewServer(mockRequestHandler(requestHandlers)) + defer mockServer.Close() + + tzs := tezosSubscription{ + endpoint: mockServer.URL, + } t.Run("returns error if json is invalid", func(t *testing.T) { json := []byte("{") - - _, err := extractEventsFromBlock(json, addresses, "test123") + _, err := tzs.extractEventsFromBlock(json, addresses, "test123") assert.NotNil(t, err) - }) t.Run("returns error if json is in unexpected shape", func(t *testing.T) { json := []byte("[[]]") - - _, err := extractEventsFromBlock(json, addresses, "test123") + _, err := tzs.extractEventsFromBlock(json, addresses, "test123") assert.NotNil(t, err) }) t.Run("returns no events if the address doesn't match", func(t *testing.T) { json := userInitiatedSampleJSON - - events, err := extractEventsFromBlock(json, []string{"notAnAddress"}, "test123") + events, err := tzs.extractEventsFromBlock(json, []string{"notAnAddress"}, "test123") assert.Nil(t, err) assert.Len(t, events, 0) }) - t.Run("extracts SC-initiated calls to matching addresses", + t.Run("extract request parameters from internal call (on_token_tranfer)", func(t *testing.T) { js := scInitiatedSampleJSON - - events, err := extractEventsFromBlock(js, addresses, "test123") + events, err := tzs.extractEventsFromBlock(js, addresses, "mock") require.NoError(t, err) require.Len(t, events, 1) assert.IsType(t, []subscriber.Event{}, events) - assert.Equal(t, "XTZ", gjson.GetBytes(events[0], "from").Str) - assert.Equal(t, "USD", gjson.GetBytes(events[0], "to").Str) - assert.Equal(t, "9", gjson.GetBytes(events[0], "request_id").Str) + assert.Equal(t, "EUR", gjson.GetBytes(events[0], "parameters.from").Str) + assert.Equal(t, "XTZ", gjson.GetBytes(events[0], "parameters.to").Str) + assert.Equal(t, "f6ab2c039df8049391a4c09effa97861d8de2400aea24c6d1e55961e389fd726", gjson.GetBytes(events[0], requestFieldID).Str) }) } + +func Test_getRequestType(t *testing.T) { + wd, _ := os.Getwd() + + scriptNormalized := path.Join(wd, "testdata/tezos/requests_type_normalized.json") + scriptNormalizedFile, err := os.Open(scriptNormalized) + require.NoError(t, err) + scriptNormalizedJSON, err := ioutil.ReadAll(scriptNormalizedFile) + require.NoError(t, err) + defer eitest.MustClose(scriptNormalizedFile) + + requestTypeNormalized := path.Join(wd, "testdata/tezos/request_type_normalized.json") + requestTypeNormalizedFile, err := os.Open(requestTypeNormalized) + require.NoError(t, err) + requestTypeNormalizedJSON, err := ioutil.ReadAll(requestTypeNormalizedFile) + require.NoError(t, err) + defer eitest.MustClose(requestTypeNormalizedFile) + + t.Run("Valid", + func(t *testing.T) { + expr, err := unmarshalExpression(json.RawMessage(scriptNormalizedJSON)) + require.NoError(t, err) + types, err := getRequestType(expr) + require.NoError(t, err) + require.JSONEq(t, string(requestTypeNormalizedJSON), prettifyJSON(types)) + }) + t.Run("Invalid", + func(t *testing.T) { + expr, err := unmarshalExpression(json.RawMessage(requestTypeNormalizedJSON)) + require.NoError(t, err) + _, err = getRequestType(expr) + require.Error(t, err) + }) +} + +func Test_extractMapKeyValues(t *testing.T) { + wd, _ := os.Getwd() + + t.Run("Valid", func(t *testing.T) { + validMapValue := path.Join(wd, "testdata/tezos/map_value.json") + validMapValueFile, err := os.Open(validMapValue) + require.NoError(t, err) + validMapValueJSON, err := ioutil.ReadAll(validMapValueFile) + require.NoError(t, err) + defer eitest.MustClose(validMapValueFile) + expression, err := convertToJSONArray(json.RawMessage(validMapValueJSON)) + require.NoError(t, err) + + keyValues, err := extractMapKeyValues(expression) + assert.NoError(t, err) + assert.Equal(t, "XTZ", keyValues["to"]) + assert.Equal(t, "EUR", keyValues["from"]) + }) + + t.Run("Invalid", func(t *testing.T) { + invalidMapValue := path.Join(wd, "testdata/tezos/invalid_map_value.json") + invalidMapValueFile, err := os.Open(invalidMapValue) + require.NoError(t, err) + invalidMapValueJSON, err := ioutil.ReadAll(invalidMapValueFile) + require.NoError(t, err) + defer eitest.MustClose(invalidMapValueFile) + expression, err := convertToJSONArray(json.RawMessage(invalidMapValueJSON)) + require.NoError(t, err) + + keyValues, err := extractMapKeyValues(expression) + assert.Error(t, err) + assert.Equal(t, 0, len(keyValues)) + }) +} + +func Test_getAnnotation(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + annotation, err := getAnnotation([]string{"%valid_annotation"}) + assert.NoError(t, err) + assert.EqualValues(t, "valid_annotation", annotation) + + annotation, err = getAnnotation([]string{"%_valid_annotation"}) + assert.NoError(t, err) + assert.EqualValues(t, "_valid_annotation", annotation) + + annotation, err = getAnnotation([]string{"%a"}) + assert.NoError(t, err) + assert.Equal(t, "a", annotation) + }) + t.Run("Invalid", func(t *testing.T) { + annotation, err := getAnnotation([]string{""}) + assert.Error(t, err) + assert.EqualValues(t, "", annotation) + + annotation, err = getAnnotation([]string{"@invalid_annotation"}) + assert.Error(t, err) + assert.Equal(t, "", annotation) + }) +} + +func Test_extractVariantValue(t *testing.T) { + t.Run("Bool Variants", func(t *testing.T) { + expression := michelsonExpression{ + Prim: "True", + } + value, err := extractVariantValue(expression) + assert.NoError(t, err) + assert.Equal(t, true, value) + + expression = michelsonExpression{ + Prim: "False", + } + value, err = extractVariantValue(expression) + assert.NoError(t, err) + assert.Equal(t, false, value) + }) + t.Run("String Variant", func(t *testing.T) { + expression := michelsonExpression{ + Prim: "Right", + Args: []json.RawMessage{ + json.RawMessage(` + { + "prim": "Right", + "args": [ + { + "string": "XTZ" + } + ] + } + `), + }, + } + value, err := extractVariantValue(expression) + assert.NoError(t, err) + assert.Equal(t, "XTZ", value) + }) + t.Run("Int Variant", func(t *testing.T) { + expression := michelsonExpression{ + Prim: "Right", + Args: []json.RawMessage{ + json.RawMessage(`{ "int": "10" }`), + }, + } + value, err := extractVariantValue(expression) + assert.NoError(t, err) + assert.Equal(t, int64(10), value) + }) + t.Run("Bytes Variant", func(t *testing.T) { + expression := michelsonExpression{ + Prim: "Right", + Args: []json.RawMessage{ + json.RawMessage(` + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "bytes": "74657a6f73" + } + ] + } + ] + } + `), + }, + } + value, err := extractVariantValue(expression) + assert.NoError(t, err) + assert.Equal(t, "74657a6f73", value) + }) + t.Run("Invalid", func(t *testing.T) { + expression := michelsonExpression{} + value, err := extractVariantValue(expression) + assert.Error(t, err) + assert.Equal(t, nil, value) + }) +} + +// --------- +// Helpers +// --------- +type RequestHandler struct { + RegPath *regexp.Regexp + Response []byte +} + +func mockRequestHandler(rh []RequestHandler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for _, req := range rh { + if req.RegPath.MatchString(r.URL.String()) { + w.Write(req.Response) + return + } + } + }) +} + +func prettifyJSON(o interface{}) string { + prettyJSON, _ := json.MarshalIndent(o, "", " ") + return string(prettyJSON) +} diff --git a/integration/mock-client/blockchain/static/xtz.json b/integration/mock-client/blockchain/static/xtz.json index 72d05fbc..efd03679 100644 --- a/integration/mock-client/blockchain/static/xtz.json +++ b/integration/mock-client/blockchain/static/xtz.json @@ -5,20 +5,20 @@ [], [ { - "protocol": "nonsense", - "chainID": "main", - "hash": "8BADF00D8BADF00D8BADF00D8BADF00D8BADF00D8BADF00D8BADF00D", - "branch": "8BADF00D", + "protocol": "PtEdo2ZkT9oKpimTah6x2embF25oss54njMuPzkJTEi5RqfdZFA", + "chain_id": "NetXnuwTfg9Box8", + "hash": "oodhhKgjJPAaktvrByGWoNyTPEFvvMtEzVfbdVav5jJP7szJSiC", + "branch": "BLCww5q5XV1ZixzDGEDBvncYF6nzgHgGS1yjbQ8T7yMzFV1ZSrZ", "contents": [ { "kind": "transaction", - "source": "tz1Q8dtxfG6AbAnYtaFLwcw6WPYCCntbf2Jk", - "fee": "100000", - "counter": "1469017", - "gas_limit": "750000", + "source": "tz1evBmfWZoPDN38avoRGbjJaLBJUP8AZz6a", + "fee": "200000", + "counter": "11", + "gas_limit": "1040000", "storage_limit": "1000", "amount": "0", - "destination": "KT1EfcYSSmqcLthMH1uwYn1mDjML1gsX2SP2", + "destination": "KT1Vz7PE3QEtw8F4UF3SnJFb97sgD9fZxSdr", "parameters": { "entrypoint": "default", "value": { @@ -28,7 +28,88 @@ "prim": "Left", "args": [ { - "prim": "Unit" + "prim": "Pair", + "args": [ + { + "string": "KT1Vz7PE3QEtw8F4UF3SnJFb97sgD9fZxSdr%set_value" + }, + { + "prim": "Pair", + "args": [ + { + "int": "5" + }, + { + "prim": "Pair", + "args": [ + { + "string": "jobid" + }, + { + "prim": "Pair", + "args": [ + { + "string": "KT1TuXZYMD9tf5Bbv99cpZMB9sy1kbcZJKTk" + }, + { + "prim": "Pair", + "args": [ + [ + { + "prim": "Elt", + "args": [ + { + "string": "from" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "EUR" + } + ] + } + ] + } + ] + }, + { + "prim": "Elt", + "args": [ + { + "string": "to" + }, + { + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "XTZ" + } + ] + } + ] + } + ] + } + ], + { + "int": "3" + } + ] + } + ] + } + ] + } + ] + } + ] } ] } @@ -39,203 +120,109 @@ "balance_updates": [ { "kind": "contract", - "contract": "tz1Q8dtxfG6AbAnYtaFLwcw6WPYCCntbf2Jk", - "change": "-100000" + "contract": "tz1evBmfWZoPDN38avoRGbjJaLBJUP8AZz6a", + "change": "-200000" }, { "kind": "freezer", "category": "fees", - "delegate": "tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU", - "cycle": 277, - "change": "100000" + "delegate": "tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU", + "cycle": 37, + "change": "200000" } ], "operation_result": { "status": "applied", - "storage": { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "bytes": "00007539e349d38f3762a7f7ce381e6e36a986090696" - }, - { - "prim": "Pair", - "args": [ - { - "int": "2" - }, - { - "bytes": "019b1c03bed5e07b153bbff1bf216f5d621a0767d500" - } - ] - } - ] + "storage": [ + { + "prim": "Pair", + "args": [ + { + "bytes": "0000d3789f9f4223c271403bde738fd304691e796ee8" + }, + { + "prim": "None" + } + ] + }, + { + "int": "2" + }, + { + "int": "8" + }, + { + "bytes": "019e5225d7e53fce0fe227ced8286460e91f78cd3e00" + } + ], + "big_map_diff": [ + { + "action": "update", + "big_map": "8", + "key_hash": "expru2dKqDfZG8hu4wNGkiyunvq2hdSKuVYtcKta7BWP6Q18oNxKjS", + "key": { + "int": "1" }, - { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "bytes": "012439cf083ae5f0f5c0a133a7fdf49976ce2432c300" - }, - { - "prim": "Some", - "args": [ - { - "int": "1" - } - ] - } - ] - }, - { - "prim": "Pair", - "args": [ - { - "int": "0" - }, - { - "bytes": "0001" - } - ] - } - ] + "value": { + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" } - ] - }, + } + ], "balance_updates": [ { "kind": "contract", - "contract": "tz1Q8dtxfG6AbAnYtaFLwcw6WPYCCntbf2Jk", - "change": "-2000" + "contract": "tz1evBmfWZoPDN38avoRGbjJaLBJUP8AZz6a", + "change": "-23000" } ], - "consumed_gas": "224595", - "storage_size": "2439", - "paid_storage_size_diff": "2" + "consumed_gas": "31295", + "consumed_milligas": "31294423", + "storage_size": "2029", + "paid_storage_size_diff": "92", + "lazy_storage_diff": [ + { + "kind": "big_map", + "id": "8", + "diff": { + "action": "update", + "updates": [ + { + "key_hash": "expru2dKqDfZG8hu4wNGkiyunvq2hdSKuVYtcKta7BWP6Q18oNxKjS", + "key": { + "int": "1" + }, + "value": { + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" + } + } + ] + } + } + ] }, "internal_operation_results": [ { "kind": "transaction", - "source": "KT1EfcYSSmqcLthMH1uwYn1mDjML1gsX2SP2", + "source": "KT1Vz7PE3QEtw8F4UF3SnJFb97sgD9fZxSdr", "nonce": 0, "amount": "0", - "destination": "KT1BtK9HryqaQ5kXzhsJvDfUZfcpwsdFpMQN", + "destination": "KT1P1tjogGsCrGBYX2VdG31bbxrhQ1a7YBfY", "parameters": { - "entrypoint": "proxy", + "entrypoint": "transfer_and_call", "value": { "prim": "Pair", "args": [ { - "bytes": "019b1c03bed5e07b153bbff1bf216f5d621a0767d500" + "int": "3" }, { "prim": "Pair", "args": [ { - "prim": "Pair", - "args": [ - { - "int": "2" - }, - { - "prim": "Pair", - "args": [ - { - "int": "1" - }, - { - "bytes": "0001" - } - ] - } - ] + "bytes": "0507070a0000001f01eac4c08634d9ea12e95736216df49b366e6f39ab007365745f76616c7565070700af9891880c07070a0000001601eac4c08634d9ea12e95736216df49b366e6f39ab0007070002070701000000056a6f62696407070a0000001601d3f69a73c4ccba87b23885bb2487591f21ab355c00020000002c0704010000000466726f6d05080508010000000345555207040100000002746f05080508010000000358545a" }, { - "prim": "Pair", - "args": [ - [ - { - "prim": "Elt", - "args": [ - { - "string": "from" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Right", - "args": [ - { - "string": "XTZ" - } - ] - } - ] - } - ] - }, - { - "prim": "Elt", - "args": [ - { - "string": "multiply" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Left", - "args": [ - { - "int": "10000" - } - ] - } - ] - } - ] - }, - { - "prim": "Elt", - "args": [ - { - "string": "to" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Right", - "args": [ - { - "string": "USD" - } - ] - } - ] - } - ] - } - ], - { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae007365745f78747a757364" - }, - { - "int": "1594815460" - } - ] - } - ] + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" } ] } @@ -244,225 +231,156 @@ }, "result": { "status": "applied", - "storage": { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "bytes": "000065c1192ac41c582a0492b1f2ca1cc9fee790a7f1" - }, - { - "prim": "Pair", - "args": [ - { - "int": "1" - }, - { - "int": "10059" - } - ] - } - ] - }, - { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ - { - "prim": "Unit" - }, - { - "int": "10060" - } - ] - }, - { - "prim": "Pair", - "args": [ - { - "prim": "False" - }, - { - "int": "10061" - } - ] - } - ] - } - ] - }, + "storage": [ + { + "prim": "Pair", + "args": [ + { + "bytes": "0000d3789f9f4223c271403bde738fd304691e796ee8" + }, + { + "prim": "Pair", + "args": [ + { + "int": "0" + }, + { + "int": "0" + } + ] + } + ] + }, + { + "prim": "Pair", + "args": [ + { + "int": "1" + }, + { + "int": "2" + } + ] + }, + { + "prim": "False" + }, + { + "int": "3" + } + ], "big_map_diff": [ { "action": "update", - "big_map": "10059", - "key_hash": "exprvHAVWporVuKLxDEgnkLqcGy7sMZdRU7oJ7Wbr3YsKXLbbAATRN", + "big_map": "0", + "key_hash": "exprthJ86cgnpvx2iAQWREmkv7fnXYsAQt3pkLjFQaSbAJgVJHAyF3", "key": { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae00" - }, - { - "int": "0" - } - ] + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" }, "value": { - "int": "998" + "int": "3" } }, { "action": "update", - "big_map": "10059", - "key_hash": "exprthGoc6NKnqtNfpHzudy3qKLTxLMTKzAaXagiqn1qL2KYNQQztR", + "big_map": "0", + "key_hash": "expru2kJjcACHrSuvM84uXoCyttb4ZkpgxRBfXnpxjK66Q7uW2ugPf", "key": { - "prim": "Pair", - "args": [ - { - "bytes": "019b1c03bed5e07b153bbff1bf216f5d621a0767d500" - }, - { - "int": "0" - } - ] + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab00" }, "value": { - "int": "2" + "int": "499997" } } ], "balance_updates": [ { "kind": "contract", - "contract": "tz1Q8dtxfG6AbAnYtaFLwcw6WPYCCntbf2Jk", - "change": "-67000" + "contract": "tz1evBmfWZoPDN38avoRGbjJaLBJUP8AZz6a", + "change": "-16750" } ], - "consumed_gas": "258137", - "storage_size": "8903", - "paid_storage_size_diff": "67" + "consumed_gas": "38860", + "consumed_milligas": "38859602", + "storage_size": "4704", + "paid_storage_size_diff": "67", + "lazy_storage_diff": [ + { + "kind": "big_map", + "id": "3", + "diff": { + "action": "update", + "updates": [] + } + }, + { + "kind": "big_map", + "id": "2", + "diff": { + "action": "update", + "updates": [] + } + }, + { + "kind": "big_map", + "id": "1", + "diff": { + "action": "update", + "updates": [] + } + }, + { + "kind": "big_map", + "id": "0", + "diff": { + "action": "update", + "updates": [ + { + "key_hash": "expru2kJjcACHrSuvM84uXoCyttb4ZkpgxRBfXnpxjK66Q7uW2ugPf", + "key": { + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab00" + }, + "value": { + "int": "499997" + } + }, + { + "key_hash": "exprthJ86cgnpvx2iAQWREmkv7fnXYsAQt3pkLjFQaSbAJgVJHAyF3", + "key": { + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" + }, + "value": { + "int": "3" + } + } + ] + } + } + ] } }, { "kind": "transaction", - "source": "KT1BtK9HryqaQ5kXzhsJvDfUZfcpwsdFpMQN", + "source": "KT1P1tjogGsCrGBYX2VdG31bbxrhQ1a7YBfY", "nonce": 1, "amount": "0", "destination": "0x2aD9B7b9386c2f45223dDFc4A4d81C2957bAE19A", "parameters": { - "entrypoint": "create_request", + "entrypoint": "on_token_transfer", "value": { "prim": "Pair", "args": [ { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae00" + "int": "3" }, { "prim": "Pair", "args": [ { - "prim": "Pair", - "args": [ - { - "int": "2" - }, - { - "prim": "Pair", - "args": [ - { - "int": "1" - }, - { - "bytes": "mock" - } - ] - } - ] + "bytes": "0507070a0000001f01eac4c08634d9ea12e95736216df49b366e6f39ab007365745f76616c7565070700af9891880c07070a0000001601eac4c08634d9ea12e95736216df49b366e6f39ab0007070002070701000000056a6f62696407070a0000001601d3f69a73c4ccba87b23885bb2487591f21ab355c00020000002c0704010000000466726f6d05080508010000000345555207040100000002746f05080508010000000358545a" }, { - "prim": "Pair", - "args": [ - [ - { - "prim": "Elt", - "args": [ - { - "string": "from" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Right", - "args": [ - { - "string": "XTZ" - } - ] - } - ] - } - ] - }, - { - "prim": "Elt", - "args": [ - { - "string": "multiply" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Left", - "args": [ - { - "int": "10000" - } - ] - } - ] - } - ] - }, - { - "prim": "Elt", - "args": [ - { - "string": "to" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Right", - "args": [ - { - "string": "USD" - } - ] - } - ] - } - ] - } - ], - { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae007365745f78747a757364" - }, - { - "int": "1594815460" - } - ] - } - ] + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab00" } ] } @@ -474,223 +392,415 @@ "storage": { "prim": "Pair", "args": [ + [ + { + "prim": "Pair", + "args": [ + { + "prim": "True" + }, + { + "bytes": "0000d3789f9f4223c271403bde738fd304691e796ee8" + } + ] + }, + { + "int": "5" + }, + { + "int": "1" + }, + { + "bytes": "019e5225d7e53fce0fe227ced8286460e91f78cd3e00" + } + ], { - "prim": "Pair", - "args": [ + "int": "6" + } + ] + }, + "big_map_diff": [ + { + "action": "update", + "big_map": "6", + "key_hash": "exprvLkgZCFy8PFS4jNq3JnH7p4ucZtyrmyS4dCzNRXo6h1PsBekhc", + "key": { + "bytes": "f6ab2c039df8049391a4c09effa97861d8de2400aea24c6d1e55961e389fd726" + }, + "value": [ + { + "int": "3" + }, + { + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab007365745f76616c7565" + }, + { + "int": "1619142191" + }, + { + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab00" + }, + { + "int": "2" + }, + { + "string": "mock" + }, + { + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" + }, + [ { - "prim": "Pair", + "prim": "Elt", "args": [ { - "prim": "True" + "string": "from" }, { - "bytes": "00001257c4edc38abcdd5039e7313fcb87e0db42c743" + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "EUR" + } + ] + } + ] } ] }, { - "prim": "Pair", + "prim": "Elt", "args": [ { - "int": "0" + "string": "to" }, { - "int": "0" + "prim": "Right", + "args": [ + { + "prim": "Right", + "args": [ + { + "string": "XTZ" + } + ] + } + ] } ] } ] - }, - { - "prim": "Pair", - "args": [ + ] + } + ], + "balance_updates": [ + { + "kind": "contract", + "contract": "tz1evBmfWZoPDN38avoRGbjJaLBJUP8AZz6a", + "change": "-57250" + } + ], + "consumed_gas": "18604", + "consumed_milligas": "18603107", + "storage_size": "3462", + "paid_storage_size_diff": "229", + "lazy_storage_diff": [ + { + "kind": "big_map", + "id": "6", + "diff": { + "action": "update", + "updates": [ { - "prim": "Pair", - "args": [ + "key_hash": "exprvLkgZCFy8PFS4jNq3JnH7p4ucZtyrmyS4dCzNRXo6h1PsBekhc", + "key": { + "bytes": "f6ab2c039df8049391a4c09effa97861d8de2400aea24c6d1e55961e389fd726" + }, + "value": [ { - "int": "1" + "int": "3" }, { - "int": "10065" - } - ] - }, - { - "prim": "Pair", - "args": [ + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab007365745f76616c7565" + }, { - "int": "10066" + "int": "1619142191" }, { - "bytes": "012439cf083ae5f0f5c0a133a7fdf49976ce2432c300" - } - ] - } - ] - } - ] - }, - "big_map_diff": [ - { - "action": "update", - "big_map": "10066", - "key_hash": "exprua5vA8enk8gpXUGU85Q7BU5TGEk89VYcgspJtuKjHdkiFPMWZ9", - "key": { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae00" - }, - { - "int": "1" - } - ] - }, - "value": { - "int": "0" - } - }, - { - "action": "update", - "big_map": "10065", - "key_hash": "exprtZBwZUeYYYfUs9B9Rg2ywHezVHnCCnmF9WsDQVrs582dSK63dC", - "key": { - "int": "0" - }, - "value": { - "prim": "Pair", - "args": [ - { - "prim": "Pair", - "args": [ + "bytes": "01eac4c08634d9ea12e95736216df49b366e6f39ab00" + }, { "int": "2" }, { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae00" - }, - { - "int": "1" - } - ] - } - ] - }, - { - "prim": "Pair", - "args": [ + "string": "jobid" + }, { - "prim": "Pair", - "args": [ - { - "bytes": "test123" - }, - [ + "bytes": "01d3f69a73c4ccba87b23885bb2487591f21ab355c00" + }, + [ + { + "prim": "Elt", + "args": [ { - "prim": "Elt", - "args": [ - { - "string": "from" - }, - { - "prim": "Right", - "args": [ - { - "prim": "Right", - "args": [ - { - "string": "XTZ" - } - ] - } - ] - } - ] + "string": "from" }, { - "prim": "Elt", + "prim": "Right", "args": [ - { - "string": "multiply" - }, { "prim": "Right", "args": [ { - "prim": "Left", - "args": [ - { - "int": "10000" - } - ] + "string": "EUR" } ] } ] + } + ] + }, + { + "prim": "Elt", + "args": [ + { + "string": "to" }, { - "prim": "Elt", + "prim": "Right", "args": [ - { - "string": "to" - }, { "prim": "Right", "args": [ { - "prim": "Right", - "args": [ - { - "string": "USD" - } - ] + "string": "XTZ" } ] } ] } ] - ] - }, - { - "prim": "Pair", - "args": [ - { - "bytes": "0142bb4d77aa56a4eb134be44597783636ffe2b8ae007365745f78747a757364" - }, - { - "int": "1594815460" - } - ] - } + } + ] ] } ] } } - ], - "balance_updates": [ - { - "kind": "contract", - "contract": "tz1Q8dtxfG6AbAnYtaFLwcw6WPYCCntbf2Jk", - "change": "-297000" - } - ], - "consumed_gas": "92331", - "storage_size": "4359", - "paid_storage_size_diff": "297" + ] } } ] } } - ] + ], + "signature": "sigTNg2bE4qzTYwbXgJbJAh7jsF3GEFGUFg2ZugSLpBWc9kJxy9QJrWNeig44nNUbMcYCmAsGb9P6GBi2xJhtFyf5QxXSuyW" } ] ], + "contractScript": { + "code": [ + { + "prim": "parameter", + "args": [] + }, + { + "prim": "storage", + "args": [ + { + "prim": "pair", + "args": [ + { + "prim": "pair", + "args": [ + { + "prim": "pair", + "args": [ + { + "prim": "bool", + "annots": [ + "%active" + ] + }, + { + "prim": "address", + "annots": [ + "%adminAddress" + ] + } + ] + }, + { + "prim": "int", + "annots": [ + "%minCancelTimeout" + ] + }, + { + "prim": "nat", + "annots": [ + "%minPayment" + ] + }, + { + "prim": "address", + "annots": [ + "%tokenAddress" + ] + } + ], + "annots": [ + "%config" + ] + }, + { + "prim": "big_map", + "args": [ + { + "prim": "bytes" + }, + { + "prim": "pair", + "args": [ + { + "prim": "nat", + "annots": [ + "%payment" + ] + }, + { + "prim": "pair", + "args": [ + { + "prim": "address", + "annots": [ + "%callbackAddress" + ] + }, + { + "prim": "timestamp", + "annots": [ + "%cancelTimeout" + ] + }, + { + "prim": "address", + "annots": [ + "%clientAddress" + ] + }, + { + "prim": "nat", + "annots": [ + "%id" + ] + }, + { + "prim": "string", + "annots": [ + "%jobId" + ] + }, + { + "prim": "address", + "annots": [ + "%oracleAddress" + ] + }, + { + "prim": "map", + "args": [ + { + "prim": "string" + }, + { + "prim": "or", + "args": [ + { + "prim": "bytes", + "annots": [ + "%bytes" + ] + }, + { + "prim": "or", + "args": [ + { + "prim": "int", + "annots": [ + "%int" + ] + }, + { + "prim": "string", + "annots": [ + "%string" + ] + } + ] + } + ] + } + ], + "annots": [ + "%parameters" + ] + } + ], + "annots": [ + "%request" + ] + } + ] + } + ], + "annots": [ + "%requests" + ] + } + ] + } + ] + }, + { + "prim": "code", + "args": [] + } + ], + "storage": { + "prim": "Pair", + "args": [ + [ + { + "prim": "Pair", + "args": [ + { + "prim": "True" + }, + { + "bytes": "0000d3789f9f4223c271403bde738fd304691e796ee8" + } + ] + }, + { + "int": "5" + }, + { + "int": "1" + }, + { + "bytes": "019e5225d7e53fce0fe227ced8286460e91f78cd3e00" + } + ], + { + "int": "6" + } + ] + } + }, "monitor": { "hash": "8BADF00D8BADF00D8BADF00D8BADF00D8BADF00D8BADF00D8BADF00D", "level": 0, diff --git a/integration/mock-client/blockchain/xtz.go b/integration/mock-client/blockchain/xtz.go index deb985a2..fff983ab 100644 --- a/integration/mock-client/blockchain/xtz.go +++ b/integration/mock-client/blockchain/xtz.go @@ -13,6 +13,7 @@ import ( func setXtzRoutes(router *gin.Engine) { router.GET("/http/xtz/monitor/heads/:chain_id", handleXtzMonitorRequest) router.GET("/http/xtz/chains/main/blocks/:block_id/operations", handleXtzOperationsRequest) + router.POST("/http/xtz/chains/main/blocks/:block_id/context/contracts/:contract_id/script/normalized", handleXtzContractScriptRequest) } type xtzResponses map[string]interface{} @@ -58,3 +59,14 @@ func handleXtzOperationsRequest(c *gin.Context) { c.JSON(http.StatusOK, resp) } + +func handleXtzContractScriptRequest(c *gin.Context) { + resp, err := getXtzResponse("contractScript") + if err != nil { + logger.Error(err) + c.JSON(http.StatusBadRequest, resp) + return + } + + c.JSON(http.StatusOK, resp) +} diff --git a/integration/mock-client/blockchain/xtz_test.go b/integration/mock-client/blockchain/xtz_test.go index 861cf701..5e5f28d8 100644 --- a/integration/mock-client/blockchain/xtz_test.go +++ b/integration/mock-client/blockchain/xtz_test.go @@ -36,3 +36,16 @@ func TestGetXtzOperationsResponse(t *testing.T) { assert.Greater(t, len(fourth), 0) }) } + +func TestGetXtzContractScriptResponse(t *testing.T) { + t.Run("creates an appropriately structured mock Tezos block", + func(t *testing.T) { + resp, err := getXtzResponse("contractScript") + assert.NoError(t, err) + script, ok := resp.(map[string]interface{}) + assert.True(t, ok) + storage, ok := script["storage"].(map[string]interface{}) + assert.True(t, ok) + assert.Equal(t, storage["prim"], "Pair") + }) +}