Skip to content

Commit

Permalink
chore: system contract tests (#622)
Browse files Browse the repository at this point in the history
* Refactor system contract handling and add tests

Refactored variable `SystemContractContent` to handle byte content and renamed the string version for clarity. Added comprehensive tests for system contract functionalities, including deployment, stream acceptance and revocation, and methods validation.

* Refactor test setup for deployer injection

Refactored the test setup functions to inject the deployer address, improving consistency and reducing redundancy. This change centralizes the creation of the deployer and ensures it is correctly assigned and used across multiple test setups.

* Ensure valid Ethereum addresses and refactor test setup

Validate and clean Ethereum addresses in procedures. Refactor tests for better readability and setup initial contract states. Added checks for address length and hex validity.

* Fix nil pointer dereference in test return

* Add test for division by zero error handling

This commit adds a new test case, `testDivisionByZero`, to ensure that the system correctly handles division by zero errors during index change calculations. The test sets up a primitive stream and verifies that an error is raised when division by zero occurs, aligning with our expected behavior.
  • Loading branch information
outerlook authored Sep 24, 2024
1 parent 75eeca1 commit 5bbeac1
Show file tree
Hide file tree
Showing 9 changed files with 556 additions and 53 deletions.
5 changes: 4 additions & 1 deletion internal/contracts/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import (
)

//go:embed system_contract.kf
var SystemContractContent string
var SystemContractStringContent string

//go:embed system_contract.kf
var SystemContractContent []byte

//go:embed composed_stream_template.kf
var ComposedStreamContent []byte
Expand Down
121 changes: 88 additions & 33 deletions internal/contracts/system_contract.kf
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,25 @@ table system_streams {
#data_provider_stream_id_idx primary(data_provider, stream_id)
}

procedure get_official_stream($data_provider text, $stream_id text) private view returns (result bool) {
for $row in SELECT * FROM system_streams WHERE data_provider = $data_provider AND stream_id = $stream_id AND revoked_at IS NULL {
return $row.stream_id != null;
procedure get_official_stream($data_provider text, $stream_id text) public view returns (result bool) {
// ensure valid address
$data_provider_cleaned text := ensure_ethereum_address($data_provider);

for $row in SELECT * FROM system_streams WHERE data_provider = $data_provider_cleaned AND stream_id = $stream_id AND revoked_at IS NULL {
return $row.stream_id IS DISTINCT FROM null;
}

return false;
}

// Verify hex enforces the format `ab1234` instead of `0xab1234` so we can quickly spot errors
// it may be removed as soon as we can normalize hex inputs, but for now we can't because substring is not supported
procedure verify_hex($text text) private {
// it also checks if it's a valid hex string
procedure verify_hex($text text) private view {
$hex text := decode($text, 'hex');

if $hex == null {
error(format('invalid hex: %s', $text));
}
}

foreign procedure ext_get_metadata($key text, $only_latest bool, $ref text) returns table(
Expand Down Expand Up @@ -61,14 +68,17 @@ procedure accept_stream($data_provider text, $stream_id text) public owner {

$current_blockheight int := @height;

verify_hex($data_provider);
$verified_data_provider text := ensure_ethereum_address($data_provider);

INSERT INTO system_streams (stream_id, data_provider, accepted_at)
VALUES ($stream_id, $data_provider, $current_blockheight);
VALUES ($stream_id, $verified_data_provider, $current_blockheight);
}

procedure revoke_stream($data_provider text, $stream_id text) public owner {
$is_official_stream bool := get_official_stream($data_provider, $stream_id);
// ensure valid address
$data_provider_cleaned text := ensure_ethereum_address($data_provider);

$is_official_stream bool := get_official_stream($data_provider_cleaned, $stream_id);

// Check if
if $is_official_stream == false {
Expand All @@ -77,22 +87,30 @@ procedure revoke_stream($data_provider text, $stream_id text) public owner {

$current_blockheight int := @height;

UPDATE system_streams SET revoked_at = $current_blockheight WHERE data_provider = $data_provider AND stream_id = $stream_id;
UPDATE system_streams SET revoked_at = $current_blockheight WHERE data_provider = $data_provider_cleaned AND stream_id = $stream_id;
}

// -------------------------------------------------------------------------------------------------
// ------------------------------- FOREIGN PROCEDURES --------------------------------------------
// -------------------------------------------------------------------------------------------------

foreign procedure ext_get_record($date_from text, $date_to text, $frozen_at int) returns table(
date_value text,
value decimal(36,18)
)

foreign procedure ext_get_index($date_from text, $date_to text, $frozen_at int) returns table(
foreign procedure ext_get_index($date_from text, $date_to text, $frozen_at int, $base_date text) returns table(
date_value text,
value decimal(36,18)
)
foreign procedure ext_get_index_change($date_from text, $date_to text, $frozen_at int) returns table (
foreign procedure ext_get_index_change($date_from text, $date_to text, $frozen_at int, $base_date text, $days_interval int) returns table (
value decimal(36,18)
)

// -------------------------------------------------------------------------------------------------
// ------------------------------- UNSAFE PROCEDURES -----------------------------------------------
// -------------------------------------------------------------------------------------------------

procedure get_unsafe_record($data_provider text, $stream_id text, $date_from text, $date_to text, $frozen_at int) public view returns table(
date_value text,
value decimal(36,18)
Expand All @@ -104,34 +122,31 @@ procedure get_unsafe_record($data_provider text, $stream_id text, $date_from tex
}
}

procedure get_dbid($data_provider text, $stream_id text) private view returns (result text) {
$starts_with_0x bool := false;
for $row in SELECT $data_provider LIKE '0x%' as a {
$starts_with_0x := $row.a;
}

$data_provider_without_0x text;
procedure get_unsafe_index($data_provider text, $stream_id text, $date_from text, $date_to text, $frozen_at int, $base_date text) public view returns table(
date_value text,
value decimal(36,18)
) {
$dbid text := get_dbid($data_provider, $stream_id);

if $starts_with_0x == true {
$data_provider_without_0x := substring($data_provider, 3);
} else {
$data_provider_without_0x := $data_provider;
for $row in SELECT * FROM ext_get_index[$dbid, 'get_index']($date_from, $date_to, $frozen_at, $base_date) {
return next $row.date_value, $row.value;
}

return generate_dbid($stream_id, decode($data_provider_without_0x, 'hex'));
}

procedure get_unsafe_index($data_provider text, $stream_id text, $date_from text, $date_to text, $frozen_at int) public view returns table(
date_value text,
procedure get_unsafe_index_change($data_provider text, $stream_id text, $date_from text, $date_to text, $frozen_at int, $base_date text, $days_interval int) public view returns table(
value decimal(36,18)
) {
$dbid text := get_dbid($data_provider, $stream_id);

for $row in SELECT * FROM ext_get_index[$dbid, 'get_index']($date_from, $date_to, $frozen_at) {
return next $row.date_value, $row.value;
for $row in SELECT * FROM ext_get_index_change[$dbid, 'get_index_change']($date_from, $date_to, $frozen_at, $base_date, $days_interval) {
return next $row.value;
}
}

// -------------------------------------------------------------------------------------------------
// ------------------------------- SAFE PROCEDURES -----------------------------------------------
// -------------------------------------------------------------------------------------------------

procedure get_record($data_provider text, $stream_id text, $date_from text, $date_to text, $frozen_at int) public view returns table(
date_value text,
value decimal(36,18)
Expand All @@ -148,10 +163,9 @@ procedure get_record($data_provider text, $stream_id text, $date_from text, $dat
for $row in SELECT * FROM get_unsafe_record($data_provider, $stream_id, $date_from, $date_to, $frozen_at) {
return next $row.date_value, $row.value;
}

}

procedure get_index($data_provider text, $stream_id text, $date_from text, $date_to text, $frozen_at int) public view returns table(
procedure get_index($data_provider text, $stream_id text, $date_from text, $date_to text, $frozen_at int, $base_date text) public view returns table(
date_value text,
value decimal(36,18)
) {
Expand All @@ -163,12 +177,12 @@ procedure get_index($data_provider text, $stream_id text, $date_from text, $date

$dbid text := get_dbid($data_provider, $stream_id);

for $row in SELECT * FROM ext_get_index[$dbid, 'get_index']($date_from, $date_to, $frozen_at) {
for $row in SELECT * FROM ext_get_index[$dbid, 'get_index']($date_from, $date_to, $frozen_at, $base_date) {
return next $row.date_value, $row.value;
}
}

procedure get_index_change($data_provider text, $stream_id text, $date_from text, $date_to text, $frozen_at int) public view returns table(
procedure get_index_change($data_provider text, $stream_id text, $date_from text, $date_to text, $frozen_at int, $base_date text, $days_interval int) public view returns table(
value decimal(36,18)
) {
$is_official_stream bool := get_official_stream($data_provider, $stream_id);
Expand All @@ -179,8 +193,49 @@ procedure get_index_change($data_provider text, $stream_id text, $date_from text

$dbid text := get_dbid($data_provider, $stream_id);

for $row in SELECT * FROM ext_get_index_change[$dbid, 'get_index_change']($date_from, $date_to, $frozen_at) {
for $row in SELECT * FROM ext_get_index_change[$dbid, 'get_index_change']($date_from, $date_to, $frozen_at, $base_date, $days_interval) {
return next $row.value;
}

}

// -------------------------------------------------------------------------------------------------
// ------------------------------- HELPER PROCEDURES -----------------------------------------------
// -------------------------------------------------------------------------------------------------


procedure get_dbid($data_provider text, $stream_id text) private view returns (result text) {
$starts_with_0x bool := false;
for $row in SELECT $data_provider LIKE '0x%' as a {
$starts_with_0x := $row.a;
}

$data_provider_without_0x text;

if $starts_with_0x == true {
$data_provider_without_0x := substring($data_provider, 3);
} else {
$data_provider_without_0x := $data_provider;
}

return generate_dbid($stream_id, decode($data_provider_without_0x, 'hex'));
}

procedure ensure_ethereum_address($text text) private view returns (result text) {
$final_text_without_0x text := $text;
for $row in SELECT $text LIKE '0x%' as is_0x {
if $row.is_0x == true {
$final_text_without_0x := substring($text, 3);
}
}

// verify hex
verify_hex($final_text_without_0x);

// should have 40 characters
if length($final_text_without_0x) != 40 {
error(format('invalid address length: %s', $final_text_without_0x));
}

return $final_text_without_0x;
}
71 changes: 68 additions & 3 deletions internal/contracts/tests/index_change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package tests

import (
"context"
"github.com/truflation/tsn-db/internal/contracts/tests/utils/setup"
"testing"

"github.com/truflation/tsn-db/internal/contracts/tests/utils/setup"

"github.com/pkg/errors"
"github.com/truflation/tsn-sdk/core/util"

Expand All @@ -20,22 +21,42 @@ func TestIndexChange(t *testing.T) {
Name: "index_change_test",
SchemaFiles: []string{"../primitive_stream_template.kf"},
FunctionTests: []kwilTesting.TestFunc{
testIndexChange(t),
testYoYIndexChange(t),
withTestIndexChangeSetup(testIndexChange(t)),
withTestIndexChangeSetup(testYoYIndexChange(t)),
testDivisionByZero(t),
},
})
}

func withTestIndexChangeSetup(test func(ctx context.Context, platform *kwilTesting.Platform) error) func(ctx context.Context, platform *kwilTesting.Platform) error {
return func(ctx context.Context, platform *kwilTesting.Platform) error {
// setup deployer
deployer, err := util.NewEthereumAddressFromString("0x0000000000000000000000000000000000000123")
if err != nil {
return errors.Wrap(err, "error creating ethereum address")
}

platform.Deployer = deployer.Bytes()

return test(ctx, platform)
}
}

func testIndexChange(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error {
return func(ctx context.Context, platform *kwilTesting.Platform) error {
streamName := "primitive_stream_db_name"
streamId := util.GenerateStreamId(streamName)
dbid := utils.GenerateDBID(streamId.String(), platform.Deployer)
deployer, err := util.NewEthereumAddressFromBytes(platform.Deployer)
if err != nil {
return errors.Wrap(err, "error creating ethereum address")
}

if err := setup.SetupPrimitiveFromMarkdown(ctx, setup.MarkdownPrimitiveSetupInput{
Platform: platform,
Height: 0,
PrimitiveStreamName: streamName,
Deployer: deployer,
MarkdownData: `
| date | value |
|------------|--------|
Expand Down Expand Up @@ -104,6 +125,10 @@ func testYoYIndexChange(t *testing.T) func(ctx context.Context, platform *kwilTe
streamName := "primitive_stream_db_name"
streamId := util.GenerateStreamId(streamName)
dbid := utils.GenerateDBID(streamId.String(), platform.Deployer)
deployer, err := util.NewEthereumAddressFromBytes(platform.Deployer)
if err != nil {
return errors.Wrap(err, "error creating ethereum address")
}

/*
Here’s an example calculation for corn inflation for May 22nd 2023:
Expand All @@ -120,6 +145,7 @@ func testYoYIndexChange(t *testing.T) func(ctx context.Context, platform *kwilTe
Platform: platform,
Height: 0,
PrimitiveStreamName: streamName,
Deployer: deployer,
MarkdownData: `
| date | value |
|------------|--------|
Expand Down Expand Up @@ -179,3 +205,42 @@ func testYoYIndexChange(t *testing.T) func(ctx context.Context, platform *kwilTe
return nil
}
}

// testing division by zero
// we expect this error to happen, unless our production data expects a different behavior
func testDivisionByZero(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error {
return func(ctx context.Context, platform *kwilTesting.Platform) error {
streamName := "primitive_stream_db_name"
streamId := util.GenerateStreamId(streamName)
dbid := utils.GenerateDBID(streamId.String(), platform.Deployer)

if err := setup.SetupPrimitiveFromMarkdown(ctx, setup.MarkdownPrimitiveSetupInput{
Platform: platform,
Height: 0,
PrimitiveStreamName: streamName,
MarkdownData: `
| date | value |
|------------|--------|
| 2023-01-01 | 100.00 |
| 2023-01-02 | 0.00 |
| 2023-01-03 | 103.00 |
`,
}); err != nil {
return errors.Wrap(err, "error setting up primitive stream")
}

_, err := platform.Engine.Procedure(ctx, platform.DB, &common.ExecutionData{
Procedure: "get_index_change",
Dataset: dbid,
Args: []any{"2023-01-01", "2023-01-03", nil, nil, 1},
TransactionData: common.TransactionData{
Signer: platform.Deployer,
TxID: platform.Txid(),
Height: 0,
},
})

assert.Error(t, err, "division by zero")
return nil
}
}
10 changes: 9 additions & 1 deletion internal/contracts/tests/primitive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,18 @@ func TestPrimitiveStream(t *testing.T) {

func WithPrimitiveTestSetup(testFn func(ctx context.Context, platform *kwilTesting.Platform) error) func(ctx context.Context, platform *kwilTesting.Platform) error {
return func(ctx context.Context, platform *kwilTesting.Platform) error {
deployer, err := util.NewEthereumAddressFromString("0x0000000000000000000000000000000000000123")
if err != nil {
return errors.Wrap(err, "error creating ethereum address")
}

platform.Deployer = deployer.Bytes()

// Setup initial data
err := setup.SetupPrimitiveFromMarkdown(ctx, setup.MarkdownPrimitiveSetupInput{
err = setup.SetupPrimitiveFromMarkdown(ctx, setup.MarkdownPrimitiveSetupInput{
Platform: platform,
PrimitiveStreamName: primitiveStreamName,
Deployer: deployer,
Height: 1,
MarkdownData: `
| date | value |
Expand Down
Loading

0 comments on commit 5bbeac1

Please sign in to comment.