diff --git a/dagger/contracts/clock.nim b/dagger/contracts/clock.nim new file mode 100644 index 00000000..334d2dff --- /dev/null +++ b/dagger/contracts/clock.nim @@ -0,0 +1,40 @@ +import std/times +import pkg/ethers +import pkg/chronos +import pkg/stint + +type + Clock* = ref object + provider: Provider + subscription: Subscription + offset: int64 + started: bool + SecondsSince1970* = int64 + +proc new*(_: type Clock, provider: Provider): Clock = + Clock(provider: provider) + +proc start*(clock: Clock) {.async.} = + if clock.started: + return + clock.started = true + + proc onBlock(blck: Block) {.gcsafe, upraises:[].} = + let blockTime = blck.timestamp.truncate(int64) + let computerTime = getTime().toUnix + clock.offset = blockTime - computerTime + + onBlock(!await clock.provider.getBlock(BlockTag.latest)) + + clock.subscription = await clock.provider.subscribe(onBlock) + +proc stop*(clock: Clock) {.async.} = + if not clock.started: + return + clock.started = false + + await clock.subscription.unsubscribe() + +proc now*(clock: Clock): SecondsSince1970 = + doAssert clock.started, "clock should be started before calling now()" + getTime().toUnix + clock.offset diff --git a/tests/contracts/testClock.nim b/tests/contracts/testClock.nim new file mode 100644 index 00000000..95266f82 --- /dev/null +++ b/tests/contracts/testClock.nim @@ -0,0 +1,48 @@ +import std/times +import pkg/chronos +import dagger/contracts/clock +import ../ethertest + +ethersuite "Clock": + + var clock: Clock + + setup: + clock = Clock.new(provider) + await clock.start() + + teardown: + await clock.stop() + + test "returns the current time of the EVM": + let latestBlock = (!await provider.getBlock(BlockTag.latest)) + let timestamp = latestBlock.timestamp.truncate(int64) + check clock.now() == timestamp + + test "updates time with timestamp of new blocks": + let future = (getTime() + 42.years).toUnix + discard await provider.send("evm_setNextBlockTimestamp", @[%future]) + discard await provider.send("evm_mine") + check clock.now() == future + + test "updates time using wall-clock in-between blocks": + let past = clock.now() + await sleepAsync(chronos.seconds(1)) + check clock.now() == past + 1 + + test "raises when not started": + expect AssertionError: + discard Clock.new(provider).now() + + test "raises when stopped": + await clock.stop() + expect AssertionError: + discard clock.now() + + test "handles starting multiple times": + await clock.start() + await clock.start() + + test "handles stopping multiple times": + await clock.stop() + await clock.stop() diff --git a/tests/testContracts.nim b/tests/testContracts.nim index 6d99657b..0e8fde1d 100644 --- a/tests/testContracts.nim +++ b/tests/testContracts.nim @@ -3,5 +3,6 @@ import ./contracts/testContracts import ./contracts/testMarket import ./contracts/testProofs import ./contracts/testInteractions +import ./contracts/testClock {.warning[UnusedImport]:off.}