Skip to content

Commit

Permalink
Ndev 3187 block timestamp number (#414)
Browse files Browse the repository at this point in the history
  • Loading branch information
kristinaNikolaevaa authored Nov 15, 2024
1 parent f7fd01d commit fa4bcef
Show file tree
Hide file tree
Showing 8 changed files with 467 additions and 20 deletions.
127 changes: 127 additions & 0 deletions contracts/common/Block.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;


contract BlockTimestamp {
event Result(uint256 block_timestamp);
uint256 public a;
uint256 public initial_block_timestamp;

struct Data {
uint256 value1;
uint256 value2;
}
mapping(uint256 => Data) public dataByTimestamp;
event DataAdded(uint256 timestamp, uint256 value1, uint256 value2);

constructor() {
initial_block_timestamp = block.timestamp;
}

function getBlockTimestamp() public view returns (uint256) {
return block.timestamp;
}

function logTimestamp() public {
emit Result(block.timestamp);
}

function callIterativeTrx() public payable {
uint256 timestamp_before = block.timestamp;
for (uint256 i = 0; i < 800; i++) {
a = a + block.timestamp;
}
emit Result(block.timestamp);
uint256 timestamp_after = block.timestamp;

require(timestamp_after == timestamp_before, "Timestamp changed during transaction execution");

}

function addDataToMapping(uint256 _value1, uint256 _value2) public {
uint256 currentTimestamp = block.timestamp % 1000000;
for (uint256 i = 0; i < 5; i++) {
Data memory newData = Data({
value1: _value1,
value2: _value2
});
dataByTimestamp[currentTimestamp] = newData;
emit DataAdded(currentTimestamp, _value1, _value2);
currentTimestamp = currentTimestamp + 1500;
}
}

function getDataFromMapping(uint256 _timestamp) public view returns (uint256, uint256) {
Data memory retrievedData = dataByTimestamp[_timestamp];
return (retrievedData.value1, retrievedData.value2);
}

}

contract BlockTimestampDeployer {
BlockTimestamp public blockTimestamp;
event Log(address indexed addr);

constructor() {
blockTimestamp = new BlockTimestamp();
emit Log(address(blockTimestamp));
}
}


contract BlockNumber {
event Log(address indexed sender, string message);
event Result(uint256 block_number);


uint256 public initial_block_number;

struct Data {
uint256 value1;
uint256 value2;
}
mapping(uint256 => Data) public dataByNumber;
uint256 public a;

event DataAdded(uint256 number, uint256 value1, uint256 value2);

constructor() payable {
initial_block_number = block.number;
}


function getBlockNumber() public view returns (uint256) {
return block.number;
}

function logBlockNumber() public {
emit Result(block.number);
}

function callIterativeTrx() public payable {
uint256 b = 1223;
for (uint256 i = 0; i < 1000; i++) {
a = a + block.number;
}
emit Result(block.number);
}

function addDataToMapping(uint256 _value1, uint256 _value2) public {
uint256 currentNumber = block.number;
for (uint256 i = 0; i < 5; i++) {
Data memory newData = Data({
value1: _value1,
value2: _value2
});

dataByNumber[currentNumber] = newData;
emit DataAdded(currentNumber, _value1, _value2);
currentNumber = currentNumber + 1;
}
}

function getDataFromMapping(uint256 _number) public view returns (uint256, uint256) {
Data memory retrievedData = dataByNumber[_number];
return (retrievedData.value1, retrievedData.value2);
}
}
19 changes: 19 additions & 0 deletions docs/tests/audit/evm/opcodes/test_block_timestamp_block_number.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Overview

Check timestamp and block number opcodes.

# Tests list

| Test case | Description | XFailed |
|----------------------------------------------------------------------------|------------------------------------------------|---------|
| TestBlockTimestampAndNumber::test_block_timestamp_call | Check contract return timestamp | |
| TestBlockTimestampAndNumber::test_block_timestamp_simple_trx | Check simple transaction with timestamp | |
| TestBlockTimestampAndNumber::test_block_timestamp_iterative | Check iterative transaction with timestamp | |
| TestBlockTimestampAndNumber::test_block_timestamp_constructor | Check timestamp in constructor | |
| TestBlockTimestampAndNumber::test_block_timestamp_in_mapping | Check timestamp as a key in mapping | |
| TestBlockTimestampAndNumber::test_contract_deploys_contract_with_timestamp | Check contract deploys contract with timestamp | |
| TestBlockTimestampAndNumber::test_block_number_call | Check contract return block number | |
| TestBlockTimestampAndNumber::test_block_number_simple_trx | Check simple transaction with block number | |
| TestBlockTimestampAndNumber::test_block_number_iterative | Check iterative transaction with block number | |
| TestBlockTimestampAndNumber::test_block_number_constructor | Check block number in constructor | |
| TestBlockTimestampAndNumber::test_block_number_in_mapping | Check block number as a key in mapping | |
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import random

import pytest

import allure

from utils.accounts import EthAccounts
from utils.models.result import EthGetBlockByHashResult
from utils.web3client import NeonChainWeb3Client


@pytest.fixture(scope="class")
def block_timestamp_contract(web3_client, accounts):
block_timestamp_contract, receipt = web3_client.deploy_and_get_contract(
"common/Block.sol", "0.8.10", accounts[0], contract_name="BlockTimestamp"
)
return block_timestamp_contract, receipt


@pytest.fixture(scope="class")
def block_number_contract(web3_client, accounts):
block_number_contract, receipt = web3_client.deploy_and_get_contract(
"common/Block.sol", "0.8.10", accounts[0], contract_name="BlockNumber"
)
return block_number_contract, receipt


@allure.feature("Opcodes verifications")
@allure.story("Verify block timestamp and block number")
@pytest.mark.usefixtures("accounts", "web3_client")
class TestBlockTimestampAndNumber:
web3_client: NeonChainWeb3Client
accounts: EthAccounts

def test_block_timestamp_call(self, block_timestamp_contract, json_rpc_client):
contract, _ = block_timestamp_contract
last_block = json_rpc_client.send_rpc("eth_blockNumber", [])["result"]
current_timestamp = json_rpc_client.send_rpc("eth_getBlockByNumber", [last_block, False])["result"][
"timestamp"
]
assert hex(contract.functions.getBlockTimestamp().call()) >= current_timestamp

def test_block_timestamp_simple_trx(self, block_timestamp_contract, json_rpc_client):
contract, _ = block_timestamp_contract
sender_account = self.accounts[0]

tx = self.web3_client.make_raw_tx(sender_account)
instruction_tx = contract.functions.logTimestamp().build_transaction(tx)
receipt = self.web3_client.send_transaction(sender_account, instruction_tx)
response = json_rpc_client.send_rpc(method="eth_getBlockByHash", params=[receipt["blockHash"].hex(), False])
tx_block_timestamp = EthGetBlockByHashResult(**response).result.timestamp

event_logs = contract.events.Result().process_receipt(receipt)
assert hex(event_logs[0]["args"]["block_timestamp"]) <= tx_block_timestamp

def test_block_timestamp_iterative(self, block_timestamp_contract, json_rpc_client):
contract, _ = block_timestamp_contract
sender_account = self.accounts[0]

tx = self.web3_client.make_raw_tx(sender_account)
instruction_tx = contract.functions.callIterativeTrx().build_transaction(tx)
receipt = self.web3_client.send_transaction(sender_account, instruction_tx)
assert receipt["status"] == 1
assert self.web3_client.is_trx_iterative(receipt["transactionHash"].hex())

response = json_rpc_client.send_rpc(method="eth_getBlockByHash", params=[receipt["blockHash"].hex(), False])
tx_block_timestamp = EthGetBlockByHashResult(**response).result.timestamp

event_logs = contract.events.Result().process_receipt(receipt)
assert len(event_logs) == 1, "Event logs are not found"
assert hex(event_logs[0]["args"]["block_timestamp"]) <= tx_block_timestamp

def test_block_timestamp_constructor(self, block_timestamp_contract, json_rpc_client):
contract, receipt = block_timestamp_contract
response = json_rpc_client.send_rpc(method="eth_getBlockByHash", params=[receipt["blockHash"].hex(), False])
tx_block_timestamp = EthGetBlockByHashResult(**response).result.timestamp

assert hex(contract.functions.initial_block_timestamp().call()) <= tx_block_timestamp

def test_block_timestamp_in_mapping(self, block_timestamp_contract, json_rpc_client):
contract, _ = block_timestamp_contract
sender_account = self.accounts[0]

v1 = random.randint(1, 100)
v2 = random.randint(1, 100)
tx = self.web3_client.make_raw_tx(sender_account)
instruction_tx = contract.functions.addDataToMapping(v1, v2).build_transaction(tx)
receipt = self.web3_client.send_transaction(sender_account, instruction_tx)
assert self.web3_client.is_trx_iterative(receipt["transactionHash"].hex())
assert receipt["status"] == 1
response = json_rpc_client.send_rpc(method="eth_getBlockByHash", params=[receipt["blockHash"].hex(), False])
tx_block_timestamp = EthGetBlockByHashResult(**response).result.timestamp

event_logs = contract.events.DataAdded().process_receipt(receipt)
added_timestamp = event_logs[0]["args"]["timestamp"]

assert added_timestamp <= int(tx_block_timestamp, 16)
assert contract.functions.getDataFromMapping(added_timestamp).call() == [v1, v2]

def test_block_number_call(self, block_number_contract, json_rpc_client):
contract, _ = block_number_contract
current_block_number = json_rpc_client.send_rpc(method="eth_blockNumber", params=[])["result"]
assert hex(contract.functions.getBlockNumber().call()) >= current_block_number

def test_block_number_simple_trx(self, block_number_contract, json_rpc_client):
contract, _ = block_number_contract
sender_account = self.accounts[0]

tx = self.web3_client.make_raw_tx(sender_account)
instruction_tx = contract.functions.logBlockNumber().build_transaction(tx)
receipt = self.web3_client.send_transaction(sender_account, instruction_tx)
response = json_rpc_client.send_rpc(method="eth_getBlockByHash", params=[receipt["blockHash"].hex(), False])
tx_block_number = EthGetBlockByHashResult(**response).result.number

event_logs = contract.events.Result().process_receipt(receipt)
assert hex(event_logs[0]["args"]["block_number"]) <= tx_block_number

def test_block_number_iterative(self, block_number_contract, json_rpc_client):
contract, _ = block_number_contract
sender_account = self.accounts[0]

tx = self.web3_client.make_raw_tx(sender_account)
instruction_tx = contract.functions.callIterativeTrx().build_transaction(tx)
receipt = self.web3_client.send_transaction(sender_account, instruction_tx)
assert self.web3_client.is_trx_iterative(receipt["transactionHash"].hex())
response = json_rpc_client.send_rpc(method="eth_getBlockByHash", params=[receipt["blockHash"].hex(), False])
tx_block_number = EthGetBlockByHashResult(**response).result.number
event_logs = contract.events.Result().process_receipt(receipt)

assert hex(event_logs[0]["args"]["block_number"]) <= tx_block_number

def test_block_number_constructor(self, block_number_contract, json_rpc_client):
contract, receipt = block_number_contract
response = json_rpc_client.send_rpc(method="eth_getBlockByHash", params=[receipt["blockHash"].hex(), False])
tx_block_number = EthGetBlockByHashResult(**response).result.number

assert hex(contract.functions.initial_block_number().call()) <= tx_block_number

def test_contract_deploys_contract_with_timestamp(self, json_rpc_client):
deployer, receipt = self.web3_client.deploy_and_get_contract(
"common/Block.sol", "0.8.10", self.accounts[0], contract_name="BlockTimestampDeployer"
)
response = json_rpc_client.send_rpc(method="eth_getBlockByHash", params=[receipt["blockHash"].hex(), False])
tx_block_timestamp = EthGetBlockByHashResult(**response).result.timestamp

addr = deployer.events.Log().process_receipt(receipt)[0]["args"]["addr"]
contract = self.web3_client.get_deployed_contract(addr, "common/Block.sol", "BlockTimestamp")
assert hex(contract.functions.initial_block_timestamp().call()) <= tx_block_timestamp

def test_block_number_in_mapping(self, block_number_contract):
contract, _ = block_number_contract
sender_account = self.accounts[0]

tx = self.web3_client.make_raw_tx(sender_account)
v1 = random.randint(1, 100)
v2 = random.randint(1, 100)
instruction_tx = contract.functions.addDataToMapping(v1, v2).build_transaction(tx)
receipt = self.web3_client.send_transaction(sender_account, instruction_tx)
assert self.web3_client.is_trx_iterative(receipt["transactionHash"].hex())
assert receipt["status"] == 1
event_logs = contract.events.DataAdded().process_receipt(receipt)
assert len(event_logs) == 5, "Event logs are not found"
block_number_added = event_logs[0]["args"]["number"]
assert block_number_added <= receipt["blockNumber"]
assert contract.functions.getDataFromMapping(block_number_added).call() == [v1, v2]
24 changes: 24 additions & 0 deletions integration/tests/neon_evm/test_neon_core_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def test_emulate_call_contract_function(neon_api_client, operator_keypair, treas
assert result["exit_status"] == "succeed", f"The 'exit_status' field is not succeed. Result: {result}"
assert result["steps_executed"] > 0, f"Steps executed amount is 0. Result: {result}"
assert result["used_gas"] > 0, f"Used gas is less than 0. Result: {result}"
assert result["is_timestamp_number_used"] is False, f"Value for is_timestamp_number_used is wrong. Result: {result}"
assert "Hello World" in to_text(result["result"])


Expand All @@ -82,3 +83,26 @@ def test_emulate_with_small_amount_of_steps(neon_api_client, evm_loader, user_ac
user_account.eth_address.hex(), contract=None, data=contract_code, max_steps_to_execute=10
)
assert result["exit_status"] == "revert", f"The 'exit_status' field is not revert. Result: {result}"


@pytest.mark.parametrize("contract_name", ["BlockTimestamp", "BlockNumber"])
def test_emulate_call_contract_with_block_timestamp_number(contract_name, neon_api_client, operator_keypair,
treasury_pool, evm_loader):
user_account = evm_loader.make_new_user(operator_keypair)
contract = deploy_contract(
operator_keypair,
user_account,
"common/Block.sol",
evm_loader,
treasury_pool,
version="0.8.10",
contract_name=contract_name,
)

result = neon_api_client.emulate_contract_call(user_account.eth_address.hex(),
contract=contract.eth_address.hex(),
function_signature="addDataToMapping(uint256,uint256)",
params=[1, 2])

assert result["exit_status"] == "succeed", f"The 'exit_status' field is not succeed. Result: {result}"
assert result["is_timestamp_number_used"], f"Timestamp number is not used. Result: {result}"
Loading

0 comments on commit fa4bcef

Please sign in to comment.