Skip to content

Commit

Permalink
Add support for the TIME instruction (FuelLabs#2752)
Browse files Browse the repository at this point in the history
* Add support for the time instruction

* Add a test

* Add a test

* Address some comments
  • Loading branch information
mohammadfawaz authored Sep 8, 2022
1 parent d1700ad commit 095bfe0
Show file tree
Hide file tree
Showing 16 changed files with 252 additions and 0 deletions.
1 change: 1 addition & 0 deletions sway-ast/src/expr/op_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ define_op_codes!(
(Srwq, SrwqOpcode, "srwq", (addr: reg, state_addr: reg)),
(Sww, SwwOpcode, "sww", (state_addr: reg, value: reg)),
(Swwq, SwwqOpcode, "swwq", (state_addr: reg, addr: reg)),
(Time, TimeOpcode, "time", (ret: reg, height: reg)),
(Tr, TrOpcode, "tr", (contract: reg, coins: reg, asset: reg)),
(
Tro,
Expand Down
3 changes: 3 additions & 0 deletions sway-core/src/asm_lang/allocated_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ pub(crate) enum AllocatedOpcode {
SRWQ(AllocatedRegister, AllocatedRegister),
SWW(AllocatedRegister, AllocatedRegister),
SWWQ(AllocatedRegister, AllocatedRegister),
TIME(AllocatedRegister, AllocatedRegister),
TR(AllocatedRegister, AllocatedRegister, AllocatedRegister),
TRO(
AllocatedRegister,
Expand Down Expand Up @@ -252,6 +253,7 @@ impl fmt::Display for AllocatedOp {
SRWQ(a, b) => format!("srwq {} {}", a, b),
SWW(a, b) => format!("sww {} {}", a, b),
SWWQ(a, b) => format!("swwq {} {}", a, b),
TIME(a, b) => format!("time {} {}", a, b),
TR(a, b, c) => format!("tr {} {} {}", a, b, c),
TRO(a, b, c, d) => format!("tro {} {} {} {}", a, b, c, d),
ECR(a, b, c) => format!("ecr {} {} {}", a, b, c),
Expand Down Expand Up @@ -357,6 +359,7 @@ impl AllocatedOp {
SRWQ(a, b) => VmOp::SRWQ(a.to_register_id(), b.to_register_id()),
SWW (a, b) => VmOp::SWW (a.to_register_id(), b.to_register_id()),
SWWQ(a, b) => VmOp::SWWQ(a.to_register_id(), b.to_register_id()),
TIME(a, b) => VmOp::TIME(a.to_register_id(), b.to_register_id()),
TR (a, b, c) => VmOp::TR (a.to_register_id(), b.to_register_id(), c.to_register_id()),
TRO (a, b, c, d)=> VmOp::TRO (a.to_register_id(), b.to_register_id(), c.to_register_id(), d.to_register_id()),
ECR (a, b, c) => VmOp::ECR (a.to_register_id(), b.to_register_id(), c.to_register_id()),
Expand Down
10 changes: 10 additions & 0 deletions sway-core/src/asm_lang/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,15 @@ impl Op {
);
VirtualOp::SWWQ(r1, r2)
}
"time" => {
let (r1, r2) = check!(
two_regs(args, immediate, whole_op_span),
return err(warnings, errors),
warnings,
errors
);
VirtualOp::TIME(r1, r2)
}
"tr" => {
let (r1, r2, r3) = check!(
three_regs(args, immediate, whole_op_span),
Expand Down Expand Up @@ -1351,6 +1360,7 @@ impl fmt::Display for Op {
SRWQ(a, b) => format!("srwq {} {}", a, b),
SWW(a, b) => format!("sww {} {}", a, b),
SWWQ(a, b) => format!("swwq {} {}", a, b),
TIME(a, b) => format!("time {} {}", a, b),
TR(a, b, c) => format!("tr {} {} {}", a, b, c),
TRO(a, b, c, d) => format!("tro {} {} {} {}", a, b, c, d),
ECR(a, b, c) => format!("ecr {} {} {}", a, b, c),
Expand Down
11 changes: 11 additions & 0 deletions sway-core/src/asm_lang/virtual_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ pub(crate) enum VirtualOp {
SRWQ(VirtualRegister, VirtualRegister),
SWW(VirtualRegister, VirtualRegister),
SWWQ(VirtualRegister, VirtualRegister),
TIME(VirtualRegister, VirtualRegister),
TR(VirtualRegister, VirtualRegister, VirtualRegister),
TRO(
VirtualRegister,
Expand Down Expand Up @@ -217,6 +218,7 @@ impl VirtualOp {
SRWQ(r1, r2) => vec![r1, r2],
SWW(r1, r2) => vec![r1, r2],
SWWQ(r1, r2) => vec![r1, r2],
TIME(r1, r2) => vec![r1, r2],
TR(r1, r2, r3) => vec![r1, r2, r3],
TRO(r1, r2, r3, r4) => vec![r1, r2, r3, r4],
ECR(r1, r2, r3) => vec![r1, r2, r3],
Expand Down Expand Up @@ -307,6 +309,7 @@ impl VirtualOp {
SRWQ(r1, r2) => vec![r1, r2],
SWW(r1, r2) => vec![r1, r2],
SWWQ(r1, r2) => vec![r1, r2],
TIME(_r1, r2) => vec![r2],
TR(r1, r2, r3) => vec![r1, r2, r3],
TRO(r1, r2, r3, r4) => vec![r1, r2, r3, r4],
ECR(r1, r2, r3) => vec![r1, r2, r3],
Expand Down Expand Up @@ -397,6 +400,7 @@ impl VirtualOp {
SRWQ(_r1, _r2) => vec![],
SWW(_r1, _r2) => vec![],
SWWQ(_r1, _r2) => vec![],
TIME(r1, _r2) => vec![r1],
TR(_r1, _r2, _r3) => vec![],
TRO(_r1, _r2, _r3, _r4) => vec![],
ECR(_r1, _r2, _r3) => vec![],
Expand Down Expand Up @@ -756,6 +760,10 @@ impl VirtualOp {
update_reg(reg_to_reg_map, r1),
update_reg(reg_to_reg_map, r2),
),
TIME(r1, r2) => Self::TIME(
update_reg(reg_to_reg_map, r1),
update_reg(reg_to_reg_map, r2),
),
TR(r1, r2, r3) => Self::TR(
update_reg(reg_to_reg_map, r1),
update_reg(reg_to_reg_map, r2),
Expand Down Expand Up @@ -1132,6 +1140,9 @@ impl VirtualOp {
SWWQ(reg1, reg2) => {
AllocatedOpcode::SWWQ(map_reg(&mapping, reg1), map_reg(&mapping, reg2))
}
TIME(reg1, reg2) => {
AllocatedOpcode::TIME(map_reg(&mapping, reg1), map_reg(&mapping, reg2))
}
TR(reg1, reg2, reg3) => AllocatedOpcode::TR(
map_reg(&mapping, reg1),
map_reg(&mapping, reg2),
Expand Down
17 changes: 17 additions & 0 deletions sway-lib-std/src/block.sw
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,20 @@ pub fn height() -> u64 {
height: u64
}
}

/// Get the timestamp of the current block
pub fn timestamp() -> u64 {
let current_block_height = height();
asm(timestamp, height: current_block_height) {
time timestamp height;
timestamp: u64
}
}

/// Get the timestamp of block at height `block_height`
pub fn timestamp_of_block(block_height: u64) -> u64 {
asm(timestamp, height: block_height) {
time timestamp height;
timestamp: u64
}
}
1 change: 1 addition & 0 deletions sway-parse/src/expr/op_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ define_op_codes!(
(Srwq, SrwqOpcode, "srwq", (addr, state_addr)),
(Sww, SwwOpcode, "sww", (state_addr, value)),
(Swwq, SwwqOpcode, "swwq", (state_addr, addr)),
(Time, TimeOpcode, "time", (ret, height)),
(Tr, TrOpcode, "tr", (contract, coins, asset)),
(Tro, TroOpcode, "tro", (addr, output, coins, asset)),
(Ecr, EcrOpcode, "ecr", (addr, sig, hash)),
Expand Down
2 changes: 2 additions & 0 deletions test/src/sdk-harness/test_artifacts/block_test_abi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
14 changes: 14 additions & 0 deletions test/src/sdk-harness/test_artifacts/block_test_abi/Forc.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[package]]
name = 'block_test_abi'
source = 'root'
dependencies = ['std']

[[package]]
name = 'core'
source = 'path+from-root-C0259B895CF50289'
dependencies = []

[[package]]
name = 'std'
source = 'path+from-root-C0259B895CF50289'
dependencies = ['core']
8 changes: 8 additions & 0 deletions test/src/sdk-harness/test_artifacts/block_test_abi/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "block_test_abi"

[dependencies]
std = { path = "../../../../../sway-lib-std" }
11 changes: 11 additions & 0 deletions test/src/sdk-harness/test_artifacts/block_test_abi/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
library block_test_abi;

abi BlockTest {
fn get_block_height() -> u64;

fn get_timestamp() -> u64;

fn get_timestamp_of_block(block_height: u64) -> u64;

fn get_block_and_timestamp() -> (u64, u64);
}
2 changes: 2 additions & 0 deletions test/src/sdk-harness/test_projects/block/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
22 changes: 22 additions & 0 deletions test/src/sdk-harness/test_projects/block/Forc.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[[package]]
name = 'block'
source = 'root'
dependencies = [
'block_test_abi',
'std',
]

[[package]]
name = 'block_test_abi'
source = 'path+from-root-9A6BFB98DBD0352C'
dependencies = ['std']

[[package]]
name = 'core'
source = 'path+from-root-9A6BFB98DBD0352C'
dependencies = []

[[package]]
name = 'std'
source = 'path+from-root-9A6BFB98DBD0352C'
dependencies = ['core']
9 changes: 9 additions & 0 deletions test/src/sdk-harness/test_projects/block/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "block"

[dependencies]
block_test_abi = { path = "../../test_artifacts/block_test_abi" }
std = { path = "../../../../../sway-lib-std" }
115 changes: 115 additions & 0 deletions test/src/sdk-harness/test_projects/block/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use fuels::prelude::*;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::time::{sleep, Duration};

abigen!(
BlockTestContract,
"test_projects/block/out/debug/block-abi.json"
);

async fn get_block_instance() -> (BlockTestContract, ContractId) {
let wallet = launch_provider_and_get_wallet().await;
let id = Contract::deploy(
"test_projects/block/out/debug/block.bin",
&wallet,
TxParameters::default(),
StorageConfiguration::with_storage_path(Some(
"test_projects/block/out/debug/block-storage_slots.json".to_string(),
)),
)
.await
.unwrap();
let instance = BlockTestContractBuilder::new(id.to_string(), wallet).build();

(instance, id.into())
}

#[tokio::test]
async fn can_get_block_height() {
let (instance, _id) = get_block_instance().await;
let block_0 = instance.get_block_height().call().await.unwrap();
let block_1 = instance.get_block_height().call().await.unwrap();
let block_2 = instance.get_block_height().call().await.unwrap();

// Probably consecutive blocks but we may have multiple tx per block so be conservative to
// guarantee the stability of the test
assert!(block_1.value <= block_0.value + 1);
assert!(block_2.value <= block_1.value + 1);
}

#[tokio::test]
async fn can_get_timestamp() {
let (instance, _id) = get_block_instance().await;
let block_0_time = instance.get_timestamp().call().await.unwrap();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");

// This should really be zero in most cases, but be conservative to guarantee the stability of
// the test
assert!(now.as_secs() - block_0_time.value <= 1);

// Wait 1 seconds and request another block
sleep(Duration::from_secs(1)).await;
let block_1_time = instance.get_timestamp().call().await.unwrap();

// The difference should be 1 second in most cases, but be conservative to guarantee the
// stability of the test
assert!(
1 <= block_1_time.value - block_0_time.value
&& block_1_time.value - block_0_time.value <= 2
);
// Wait 2 seconds and request another block
sleep(Duration::from_secs(2)).await;
let block_2_time = instance.get_timestamp().call().await.unwrap();

// The difference should be 2 seconds in most cases, but be conservative to guarantee the
// stability of the test
assert!(
2 <= block_2_time.value - block_1_time.value
&& block_2_time.value - block_1_time.value <= 3
);
}

#[tokio::test]
async fn can_get_timestamp_of_block() {
let (instance, _id) = get_block_instance().await;

let block_0 = instance.get_block_and_timestamp().call().await.unwrap();

sleep(Duration::from_secs(1)).await;
let block_1 = instance.get_block_and_timestamp().call().await.unwrap();

sleep(Duration::from_secs(2)).await;
let block_2 = instance.get_block_and_timestamp().call().await.unwrap();

// Check that the result of `timestamp_of_block` matches the recorded result of `timestamp()`
// above called via `get_block_and_timestamp`.
assert_eq!(
instance
.get_timestamp_of_block(block_0.value.0)
.call()
.await
.unwrap()
.value,
block_0.value.1
);
assert_eq!(
instance
.get_timestamp_of_block(block_1.value.0)
.call()
.await
.unwrap()
.value,
block_1.value.1
);
assert_eq!(
instance
.get_timestamp_of_block(block_2.value.0)
.call()
.await
.unwrap()
.value,
block_2.value.1
);
}
25 changes: 25 additions & 0 deletions test/src/sdk-harness/test_projects/block/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
contract;

use std::block::{height, timestamp, timestamp_of_block};
use block_test_abi::BlockTest;

impl BlockTest for Contract {
fn get_block_height() -> u64 {
height()
}

fn get_timestamp() -> u64 {
timestamp()
}

fn get_timestamp_of_block(block_height: u64) -> u64 {
timestamp_of_block(block_height)
}

fn get_block_and_timestamp() -> (u64, u64) {
(
height(),
timestamp(),
)
}
}
1 change: 1 addition & 0 deletions test/src/sdk-harness/test_projects/harness.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Add test modules here:

mod auth;
mod block;
mod call_frames;
mod context;
mod evm;
Expand Down

0 comments on commit 095bfe0

Please sign in to comment.