From 0c5c3ef1c0d8eb56b344c88c63b67e418a9af9aa Mon Sep 17 00:00:00 2001 From: Illia Likhoshva Date: Fri, 6 Dec 2024 22:24:36 +0200 Subject: [PATCH] Update the timestamp check function Due to Ubinetic stores timestamp with milliseconds format, we should add casting to standard-like timestamp object type. Issue shown here: https://github.com/ecadlabs/taquito/issues/3093 --- smart_contracts/oracle-on-demand.jsligo | 56 ++++++++++++++++++++++++- smart_contracts/oracle-on-demand.tz | 27 ++++++++---- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/smart_contracts/oracle-on-demand.jsligo b/smart_contracts/oracle-on-demand.jsligo index 1ac2a16..e8303df 100644 --- a/smart_contracts/oracle-on-demand.jsligo +++ b/smart_contracts/oracle-on-demand.jsligo @@ -90,6 +90,8 @@ namespace Utils { // Utils // ----- // #region + // Zero timestamp for casting + export const zeroTimestamp: timestamp = 0 as timestamp; // Empty list of operations constant export const no_operations: list = []; // Disallows transfers of tez to the contract @@ -118,6 +120,13 @@ namespace UbineticOracleUtils { }; // #endregion // ----------------------------------- + + @inline + function timestampFromMilliseconds(tsAsMilliseconds: timestamp): timestamp { + const tsAsIntWoMilliseconds: int = (tsAsMilliseconds - Utils.zeroTimestamp) / 1000; + const ts: timestamp = tsAsIntWoMilliseconds + Utils.zeroTimestamp; + return ts; + } // Retrieve price from oracle @inline @@ -133,7 +142,7 @@ namespace UbineticOracleUtils { ); // Assert data is recent. Checks if response timestamp is not older than delay in seconds. Assert.Error.assert( - response.last_update_timestamp >= Tezos.get_now() - int(delay), + timestampFromMilliseconds(response.last_update_timestamp) >= Tezos.get_now() - int(delay), ErrorCodes.stale_data ); return response.price; @@ -251,10 +260,22 @@ export namespace KolibriOracleAdapter { // --------------- // #region const xtzValue = 2310000n; +const twoDays = 86_400 * 2 +// NOTE: the Ubinetic oracle store the timestamps in non-standard way, with milliseconds. +// Highlighted here: https://discord.com/channels/790468417844412456/790469864934211604/1314628665669058630 +// Issue opened here: https://github.com/ecadlabs/taquito/issues/3093 +const nowInMilliseconds = ((Tezos.get_now() - Utils.zeroTimestamp) * 1000) + Utils.zeroTimestamp; +const twoDaysEarlierInMilliseconds = ((Tezos.get_now() - twoDays) - Utils.zeroTimestamp) * 1000 + Utils.zeroTimestamp; const mockedOracle = contract_of(Mocks.FakeOnDemandOracle); const oracleStorage: Mocks.FakeOnDemandOracle.storage = { prices: Big_map.literal([ - ["XTZUSDT", {price: xtzValue, last_update_timestamp: Tezos.get_now()}], + ["XTZUSDT", {price: xtzValue, last_update_timestamp: nowInMilliseconds}], + ]) + }; + +const oracleStorageOutdated: Mocks.FakeOnDemandOracle.storage = { + prices: Big_map.literal([ + ["XTZUSDT", {price: xtzValue, last_update_timestamp: twoDaysEarlierInMilliseconds}], ]) }; const mockedCallbackReceiver = contract_of(Mocks.FakeCallbackReceiver); @@ -339,6 +360,36 @@ function test_retrieve(): unit { return Assert.assert(Test.Next.Typed_address.get_storage(callbackContract.taddr) == expectedValue); } +function test_outdated_retrieve(): unit { + Test.Next.IO.log("getXtzUsdRate - failed to retrieve outdated value"); + const fakeOracle = Test.Next.Originate.contract( + mockedOracle, + oracleStorageOutdated, + 0mutez + ); + const adapterStorage: KolibriOracleAdapter.storage = { + oracleProxyContractAddress: Test.Next.Typed_address.to_address(fakeOracle.taddr), + maxDataDelaySec: 60n * 30n, + governorContractAddress: admin_account, + }; + const adapterContract = Test.Next.Originate.contract( + adapter, + adapterStorage, + 0mutez + ); + const callbackContract = Test.Next.Originate.contract( + mockedCallbackReceiver, + callbackStorage, + 0mutez + ); + + const callback: contract = Test.Next.Typed_address.get_entrypoint("receivePriceCallback", callbackContract.taddr); + const result = Test.Next.Contract.transfer(Test.Next.Typed_address.get_entrypoint("getXtzUsdRate", adapterContract.taddr), callback, 0mutez); + match(result) { + when(Fail(_x)): Test.Next.IO.log("[Success] Failed as expected"); + when(Success(_s)): failwith("[Fail] This should not succeed") + }; +} // ------- // default // ------- @@ -460,4 +511,5 @@ const test4 = test_governor_set_governor(); const test5 = test_with_amount(); const test6 = test_not_governor_set_delay(); const test7 = test_not_governor_set_governor(); +const test8 = test_outdated_retrieve(); // #endregion \ No newline at end of file diff --git a/smart_contracts/oracle-on-demand.tz b/smart_contracts/oracle-on-demand.tz index ace039b..3b5374e 100644 --- a/smart_contracts/oracle-on-demand.tz +++ b/smart_contracts/oracle-on-demand.tz @@ -7,7 +7,7 @@ (nat %maxDataDelaySec) (address %governorContractAddress)) ; code { PUSH string "15" ; - PUSH string "4" ; + PUSH timestamp 0 ; NIL operation ; DIG 3 ; UNPAIR ; @@ -23,9 +23,8 @@ SWAP } { IF_LEFT { DIG 2 ; + DROP ; DIG 3 ; - DROP 2 ; - DIG 2 ; PUSH mutez 0 ; AMOUNT ; COMPARE ; @@ -45,8 +44,16 @@ INT ; NOW ; SUB ; - DUP 3 ; + PUSH int 1000 ; + DUP 7 ; + DUP 5 ; CDR ; + SUB ; + EDIV ; + IF_NONE { PUSH string "DIV by 0" ; FAILWITH } {} ; + CAR ; + DIG 6 ; + ADD ; COMPARE ; GE ; IF { DROP } { FAILWITH } ; @@ -61,14 +68,16 @@ DIG 4 ; TRANSFER_TOKENS ; CONS } - { IF_LEFT - { DIG 4 ; + { DIG 3 ; + DROP ; + IF_LEFT + { DIG 3 ; PUSH mutez 0 ; AMOUNT ; COMPARE ; EQ ; IF { DROP } { FAILWITH } ; - DIG 3 ; + PUSH string "4" ; DUP 3 ; GET 4 ; SENDER ; @@ -76,13 +85,13 @@ EQ ; IF { DROP } { FAILWITH } ; UPDATE 4 } - { DIG 4 ; + { DIG 3 ; PUSH mutez 0 ; AMOUNT ; COMPARE ; EQ ; IF { DROP } { FAILWITH } ; - DIG 3 ; + PUSH string "4" ; DUP 3 ; GET 4 ; SENDER ;