From 96eb7f146c8703e76df8ff1187f715178fb75a13 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Thu, 23 May 2024 15:56:12 +0700 Subject: [PATCH 01/26] implement logic and unit tests --- examples/gno.land/p/demo/queue/errors.gno | 12 + examples/gno.land/p/demo/queue/gno.mod | 1 + examples/gno.land/p/demo/queue/queue.gno | 118 ++++++ examples/gno.land/p/demo/queue/queue_test.gno | 346 ++++++++++++++++++ 4 files changed, 477 insertions(+) create mode 100644 examples/gno.land/p/demo/queue/errors.gno create mode 100644 examples/gno.land/p/demo/queue/gno.mod create mode 100644 examples/gno.land/p/demo/queue/queue.gno create mode 100644 examples/gno.land/p/demo/queue/queue_test.gno diff --git a/examples/gno.land/p/demo/queue/errors.gno b/examples/gno.land/p/demo/queue/errors.gno new file mode 100644 index 00000000000..33c637ff95b --- /dev/null +++ b/examples/gno.land/p/demo/queue/errors.gno @@ -0,0 +1,12 @@ +package queue + +import ( + "errors" +) + +// Error definitions for various edge cases +var ( + ErrResourceError = errors.New("resource error: queue is full") + ErrEmptyArrayPop = errors.New("empty array pop: queue is empty") + ErrArrayOutOfBounds = errors.New("array out of bounds") +) diff --git a/examples/gno.land/p/demo/queue/gno.mod b/examples/gno.land/p/demo/queue/gno.mod new file mode 100644 index 00000000000..02bf2e6a203 --- /dev/null +++ b/examples/gno.land/p/demo/queue/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/queue \ No newline at end of file diff --git a/examples/gno.land/p/demo/queue/queue.gno b/examples/gno.land/p/demo/queue/queue.gno new file mode 100644 index 00000000000..122161cd1a4 --- /dev/null +++ b/examples/gno.land/p/demo/queue/queue.gno @@ -0,0 +1,118 @@ +package queue + +// Queue is a queue data structure that stores elements of any type. +type Queue struct { + begin uint64 // Index of the front element + end uint64 // Index after the last element + data map[uint64]interface{} // Storage for queue elements +} + +// NewQueue creates and initializes a new queue. +func NewQueue() *Queue { + return &Queue{ + begin: 0, + end: 0, + data: make(map[uint64]interface{}), + } +} + +// PushBack adds an element to the end of the queue. +// Returns an error if the queue is full. +func (q *Queue) PushBack(value interface{}) error { + if q.end+1 == q.begin { + return ErrResourceError + } + + q.data[q.end] = value + q.end++ + + return nil +} + +// PopBack removes and returns the element at the end of the queue. +// Returns an error if the queue is empty. +func (q *Queue) PopBack() (interface{}, error) { + if q.begin == q.end { + return nil, ErrEmptyArrayPop + } + + q.end-- + value := q.data[q.end] + delete(q.data, q.end) + + return value, nil +} + +// PushFront adds an element to the front of the queue. +// Returns an error if the queue is full. +func (q *Queue) PushFront(value interface{}) error { + if q.begin-1 == q.end { + return ErrResourceError + } + + q.begin-- + q.data[q.begin] = value + + return nil +} + +// PopFront removes and returns the element at the front of the queue. +// Returns an error if the queue is empty. +func (q *Queue) PopFront() (interface{}, error) { + if q.begin == q.end { + return nil, ErrEmptyArrayPop + } + + value := q.data[q.begin] + delete(q.data, q.begin) + q.begin++ + + return value, nil +} + +// Front returns the element at the front of the queue without removing it. +// Returns an error if the queue is empty. +func (q *Queue) Front() (interface{}, error) { + if q.Empty() { + return nil, ErrArrayOutOfBounds + } + + return q.data[q.begin], nil +} + +// Back returns the element at the end of the queue without removing it. +// Returns an error if the queue is empty. +func (q *Queue) Back() (interface{}, error) { + if q.Empty() { + return nil, ErrArrayOutOfBounds + } + + return q.data[q.end-1], nil +} + +// At returns the element at the specified index in the queue. +// Returns an error if the index is out of bounds. +func (q *Queue) At(index uint64) (interface{}, error) { + if index >= q.Length() { + return nil, ErrArrayOutOfBounds + } + + return q.data[q.begin+index], nil +} + +// Clear removes all elements from the queue. +func (q *Queue) Clear() { + q.begin = 0 + q.end = 0 + q.data = make(map[uint64]interface{}) +} + +// Length returns the number of elements in the queue. +func (q *Queue) Length() uint64 { + return q.end - q.begin +} + +// Empty returns true if the queue is empty, false otherwise. +func (q *Queue) Empty() bool { + return q.end == q.begin +} diff --git a/examples/gno.land/p/demo/queue/queue_test.gno b/examples/gno.land/p/demo/queue/queue_test.gno new file mode 100644 index 00000000000..eb4cd1c154d --- /dev/null +++ b/examples/gno.land/p/demo/queue/queue_test.gno @@ -0,0 +1,346 @@ +package queue + +import ( + "testing" +) + +func TestPushBack(t *testing.T) { + // Initialize a new queue + queue := NewQueue() + + // Test pushing an element to the back of the queue + err := queue.PushBack("first") + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + // Test pushing another element to the back of the queue + err = queue.PushBack("second") + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if queue.Length() != 2 { + t.Errorf("Expected length 2, got: %d", queue.Length()) + } +} + +func TestPopBack(t *testing.T) { + // Initialize a new queue + queue := NewQueue() + + // Add two elements to the queue + queue.PushBack("first") + queue.PushBack("second") + + // Test popping the last element from the queue + value, err := queue.PopBack() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "second" { + t.Errorf("Expected 'second', got: %s", value) + } + + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + // Test popping the last remaining element from the queue + value, err = queue.PopBack() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "first" { + t.Errorf("Expected 'first', got: %s", value) + } + + if queue.Length() != 0 { + t.Errorf("Expected length 0, got: %d", queue.Length()) + } + + // Test popping from an empty queue, should return an error + _, err = queue.PopBack() + if err != ErrEmptyArrayPop { + t.Errorf("Expected error %s, got: %s", ErrEmptyArrayPop, err.Error()) + } +} + +func TestPushFront(t *testing.T) { + // Initialize a new queue + queue := NewQueue() + + // Test pushing an element to the front of the queue + err := queue.PushFront("first") + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + // Test pushing another element to the front of the queue + err = queue.PushFront("second") + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if queue.Length() != 2 { + t.Errorf("Expected length 2, got: %d", queue.Length()) + } +} + +func TestPopFront(t *testing.T) { + // Initialize a new queue + queue := NewQueue() + + // Add two elements to the queue + queue.PushBack("first") + queue.PushBack("second") + + // Test popping the front element from the queue + value, err := queue.PopFront() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "first" { + t.Errorf("Expected 'first', got: %s", value) + } + + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + // Test popping the next front element from the queue + value, err = queue.PopFront() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "second" { + t.Errorf("Expected 'second', got: %s", value) + } + + if queue.Length() != 0 { + t.Errorf("Expected length 0, got: %d", queue.Length()) + } + + // Test popping from an empty queue, should return an error + _, err = queue.PopFront() + if err != ErrEmptyArrayPop { + t.Errorf("Expected error %s, got: %s", ErrEmptyArrayPop, err.Error()) + } +} + +func TestFront(t *testing.T) { + // Initialize a new queue + queue := NewQueue() + + // Add two elements to the queue + queue.PushBack("first") + queue.PushBack("second") + + // Test retrieving the front element without removing it + value, err := queue.Front() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "first" { + t.Errorf("Expected 'first', got: %s", value) + } + + // Remove the front element and test retrieving the new front element + queue.PopFront() + value, err = queue.Front() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "second" { + t.Errorf("Expected 'second', got: %s", value) + } + + // Remove the remaining element and test retrieving the front from an empty queue, should return an error + queue.PopFront() + _, err = queue.Front() + if err != ErrArrayOutOfBounds { + t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) + } +} + +func TestBack(t *testing.T) { + // Initialize a new queue + queue := NewQueue() + + // Add two elements to the queue + queue.PushBack("first") + queue.PushBack("second") + + // Test retrieving the last element without removing it + value, err := queue.Back() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "second" { + t.Errorf("Expected 'second', got: %s", value) + } + + // Remove the last element and test retrieving the new last element + queue.PopBack() + value, err = queue.Back() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "first" { + t.Errorf("Expected 'first', got: %s", value) + } + + // Remove the remaining element and test retrieving the last from an empty queue, should return an error + queue.PopBack() + _, err = queue.Back() + if err != ErrArrayOutOfBounds { + t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) + } +} + +func TestAt(t *testing.T) { + // Initialize a new queue + queue := NewQueue() + + // Add three elements to the queue + queue.PushBack("first") + queue.PushBack("second") + queue.PushBack("third") + + // Test accessing elements at specific indices + value, err := queue.At(0) + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "first" { + t.Errorf("Expected 'first', got: %s", value) + } + + value, err = queue.At(1) + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "second" { + t.Errorf("Expected 'second', got: %s", value) + } + + value, err = queue.At(2) + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "third" { + t.Errorf("Expected 'third', got: %s", value) + } + + // Test accessing an out-of-bounds index, should return an error + _, err = queue.At(3) + if err != ErrArrayOutOfBounds { + t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) + } +} + +func TestClear(t *testing.T) { + // Initialize a new queue + queue := NewQueue() + + // Add two elements to the queue + queue.PushBack("first") + queue.PushBack("second") + + // Clear the queue + queue.Clear() + + // Test if the queue is empty after clearing + if queue.Length() != 0 { + t.Errorf("Expected length 0, got: %d", queue.Length()) + } + + if !queue.Empty() { + t.Errorf("Expected queue to be empty") + } + + // Test retrieving the front from an empty queue, should return an error + _, err := queue.Front() + if err != ErrArrayOutOfBounds { + t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) + } + + // Test retrieving the back from an empty queue, should return an error + _, err = queue.Back() + if err != ErrArrayOutOfBounds { + t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) + } +} + +func TestLengthAndEmpty(t *testing.T) { + // Initialize a new queue + queue := NewQueue() + + // Test initial length and empty status + if queue.Length() != 0 { + t.Errorf("Expected length 0, got: %d", queue.Length()) + } + + if !queue.Empty() { + t.Errorf("Expected queue to be empty") + } + + // Add an element and test length and empty status + queue.PushBack("first") + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + if queue.Empty() { + t.Errorf("Expected queue not to be empty") + } + + // Add another element and test length and empty status + queue.PushBack("second") + if queue.Length() != 2 { + t.Errorf("Expected length 2, got: %d", queue.Length()) + } + + if queue.Empty() { + t.Errorf("Expected queue not to be empty") + } + + // Remove an element and test length and empty status + queue.PopFront() + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + if queue.Empty() { + t.Errorf("Expected queue not to be empty") + } + + // Remove the last element and test length and empty status + queue.PopBack() + if queue.Length() != 0 { + t.Errorf("Expected length 0, got: %d", queue.Length()) + } + + if !queue.Empty() { + t.Errorf("Expected queue to be empty") + } +} From 81f45ef857c682ce9b783fa838b1e66f5c90c0cf Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Sun, 26 May 2024 20:27:50 +0700 Subject: [PATCH 02/26] add new line --- examples/gno.land/p/demo/queue/gno.mod | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/queue/gno.mod b/examples/gno.land/p/demo/queue/gno.mod index 02bf2e6a203..ca875d22b51 100644 --- a/examples/gno.land/p/demo/queue/gno.mod +++ b/examples/gno.land/p/demo/queue/gno.mod @@ -1 +1,2 @@ -module gno.land/p/demo/queue \ No newline at end of file +module gno.land/p/demo/queue + From 450f3ae96616907d80bfeddf95dc1d26de0c1382 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Sun, 26 May 2024 20:33:15 +0700 Subject: [PATCH 03/26] make tidy --- examples/gno.land/p/demo/queue/gno.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/gno.land/p/demo/queue/gno.mod b/examples/gno.land/p/demo/queue/gno.mod index ca875d22b51..1b4650ac4a0 100644 --- a/examples/gno.land/p/demo/queue/gno.mod +++ b/examples/gno.land/p/demo/queue/gno.mod @@ -1,2 +1 @@ module gno.land/p/demo/queue - From a115de030bf96f7b29e31b823a262b70bad010dd Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Mon, 27 May 2024 09:33:05 +0700 Subject: [PATCH 04/26] use avl.Tree & seqid --- examples/gno.land/p/demo/queue/errors.gno | 2 + examples/gno.land/p/demo/queue/queue.gno | 70 +++++++++++++++++------ 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/examples/gno.land/p/demo/queue/errors.gno b/examples/gno.land/p/demo/queue/errors.gno index 33c637ff95b..195f9647ea5 100644 --- a/examples/gno.land/p/demo/queue/errors.gno +++ b/examples/gno.land/p/demo/queue/errors.gno @@ -9,4 +9,6 @@ var ( ErrResourceError = errors.New("resource error: queue is full") ErrEmptyArrayPop = errors.New("empty array pop: queue is empty") ErrArrayOutOfBounds = errors.New("array out of bounds") + + ErrNonExistedValue = errors.New("non-existed value") ) diff --git a/examples/gno.land/p/demo/queue/queue.gno b/examples/gno.land/p/demo/queue/queue.gno index 122161cd1a4..97bd2406806 100644 --- a/examples/gno.land/p/demo/queue/queue.gno +++ b/examples/gno.land/p/demo/queue/queue.gno @@ -1,18 +1,23 @@ package queue +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" +) + // Queue is a queue data structure that stores elements of any type. type Queue struct { - begin uint64 // Index of the front element - end uint64 // Index after the last element - data map[uint64]interface{} // Storage for queue elements + begin seqid.ID // Index of the front element + end seqid.ID // Index after the last element + data *avl.Tree // Storage for queue elements } // NewQueue creates and initializes a new queue. func NewQueue() *Queue { return &Queue{ - begin: 0, - end: 0, - data: make(map[uint64]interface{}), + begin: seqid.ID(0), + end: seqid.ID(0), + data: avl.NewTree(), } } @@ -23,7 +28,8 @@ func (q *Queue) PushBack(value interface{}) error { return ErrResourceError } - q.data[q.end] = value + q.data.Set(q.end.String(), value) + q.end++ return nil @@ -37,8 +43,12 @@ func (q *Queue) PopBack() (interface{}, error) { } q.end-- - value := q.data[q.end] - delete(q.data, q.end) + value, ok := q.data.Get(q.end.String()) + if !ok { + return nil, ErrNonExistedValue + } + + q.data.Remove(q.end.String()) return value, nil } @@ -51,7 +61,7 @@ func (q *Queue) PushFront(value interface{}) error { } q.begin-- - q.data[q.begin] = value + q.data.Set(q.begin.String(), value) return nil } @@ -63,8 +73,13 @@ func (q *Queue) PopFront() (interface{}, error) { return nil, ErrEmptyArrayPop } - value := q.data[q.begin] - delete(q.data, q.begin) + value, ok := q.data.Get(q.begin.String()) + if !ok { + return nil, ErrNonExistedValue + } + + q.data.Remove(q.begin.String()) + q.begin++ return value, nil @@ -77,7 +92,14 @@ func (q *Queue) Front() (interface{}, error) { return nil, ErrArrayOutOfBounds } - return q.data[q.begin], nil + id := q.begin + + value, ok := q.data.Get(id.String()) + if !ok { + return nil, ErrNonExistedValue + } + + return value, nil } // Back returns the element at the end of the queue without removing it. @@ -87,7 +109,14 @@ func (q *Queue) Back() (interface{}, error) { return nil, ErrArrayOutOfBounds } - return q.data[q.end-1], nil + id := q.end - 1 + + value, ok := q.data.Get(id.String()) + if !ok { + return nil, ErrNonExistedValue + } + + return value, nil } // At returns the element at the specified index in the queue. @@ -97,19 +126,26 @@ func (q *Queue) At(index uint64) (interface{}, error) { return nil, ErrArrayOutOfBounds } - return q.data[q.begin+index], nil + id := q.begin + seqid.ID(index) + + value, ok := q.data.Get(id.String()) + if !ok { + return nil, ErrNonExistedValue + } + + return value, nil } // Clear removes all elements from the queue. func (q *Queue) Clear() { q.begin = 0 q.end = 0 - q.data = make(map[uint64]interface{}) + q.data = avl.NewTree() } // Length returns the number of elements in the queue. func (q *Queue) Length() uint64 { - return q.end - q.begin + return uint64(q.end - q.begin) } // Empty returns true if the queue is empty, false otherwise. From 721f8f0390800274b1389b65f386a46f64a8b90d Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Mon, 27 May 2024 09:33:35 +0700 Subject: [PATCH 05/26] gno mod tidy --- examples/gno.land/p/demo/queue/gno.mod | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/gno.land/p/demo/queue/gno.mod b/examples/gno.land/p/demo/queue/gno.mod index 1b4650ac4a0..21271054ba8 100644 --- a/examples/gno.land/p/demo/queue/gno.mod +++ b/examples/gno.land/p/demo/queue/gno.mod @@ -1 +1,6 @@ module gno.land/p/demo/queue + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest +) From 8e25944753be1e49a54cf36a6de445e88a32e27b Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Tue, 25 Jun 2024 15:43:30 +0700 Subject: [PATCH 06/26] rename to dequeue --- examples/gno.land/p/demo/dequeue/dequeue.gno | 154 ++++++++ .../gno.land/p/demo/dequeue/dequeue_test.gno | 346 ++++++++++++++++++ examples/gno.land/p/demo/dequeue/errors.gno | 14 + examples/gno.land/p/demo/dequeue/gno.mod | 6 + 4 files changed, 520 insertions(+) create mode 100644 examples/gno.land/p/demo/dequeue/dequeue.gno create mode 100644 examples/gno.land/p/demo/dequeue/dequeue_test.gno create mode 100644 examples/gno.land/p/demo/dequeue/errors.gno create mode 100644 examples/gno.land/p/demo/dequeue/gno.mod diff --git a/examples/gno.land/p/demo/dequeue/dequeue.gno b/examples/gno.land/p/demo/dequeue/dequeue.gno new file mode 100644 index 00000000000..58793abaefe --- /dev/null +++ b/examples/gno.land/p/demo/dequeue/dequeue.gno @@ -0,0 +1,154 @@ +package dequeue + +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" +) + +// Dequeue is a Dequeue (double-ended queue) data structure that stores elements of any type. +type Dequeue struct { + begin seqid.ID // Index of the front element + end seqid.ID // Index after the last element + data *avl.Tree // Storage for Dequeue elements +} + +// NewDequeue creates and initializes a new Dequeue. +func NewDequeue() *Dequeue { + return &Dequeue{ + begin: seqid.ID(0), + end: seqid.ID(0), + data: avl.NewTree(), + } +} + +// PushBack adds an element to the end of the Dequeue. +// Returns an error if the Dequeue is full. +func (q *Dequeue) PushBack(value interface{}) error { + if q.end+1 == q.begin { + return ErrResourceError + } + + q.data.Set(q.end.String(), value) + + q.end++ + + return nil +} + +// PopBack removes and returns the element at the end of the Dequeue. +// Returns an error if the Dequeue is empty. +func (q *Dequeue) PopBack() (interface{}, error) { + if q.begin == q.end { + return nil, ErrEmptyArrayPop + } + + q.end-- + value, ok := q.data.Get(q.end.String()) + if !ok { + return nil, ErrNonExistedValue + } + + q.data.Remove(q.end.String()) + + return value, nil +} + +// PushFront adds an element to the front of the Dequeue. +// Returns an error if the Dequeue is full. +func (q *Dequeue) PushFront(value interface{}) error { + if q.begin-1 == q.end { + return ErrResourceError + } + + q.begin-- + q.data.Set(q.begin.String(), value) + + return nil +} + +// PopFront removes and returns the element at the front of the Dequeue. +// Returns an error if the Dequeue is empty. +func (q *Dequeue) PopFront() (interface{}, error) { + if q.begin == q.end { + return nil, ErrEmptyArrayPop + } + + value, ok := q.data.Get(q.begin.String()) + if !ok { + return nil, ErrNonExistedValue + } + + q.data.Remove(q.begin.String()) + + q.begin++ + + return value, nil +} + +// Front returns the element at the front of the Dequeue without removing it. +// Returns an error if the Dequeue is empty. +func (q *Dequeue) Front() (interface{}, error) { + if q.Empty() { + return nil, ErrArrayOutOfBounds + } + + id := q.begin + + value, ok := q.data.Get(id.String()) + if !ok { + return nil, ErrNonExistedValue + } + + return value, nil +} + +// Back returns the element at the end of the Dequeue without removing it. +// Returns an error if the Dequeue is empty. +func (q *Dequeue) Back() (interface{}, error) { + if q.Empty() { + return nil, ErrArrayOutOfBounds + } + + id := q.end - 1 + + value, ok := q.data.Get(id.String()) + if !ok { + return nil, ErrNonExistedValue + } + + return value, nil +} + +// At returns the element at the specified index in the Dequeue. +// Returns an error if the index is out of bounds. +func (q *Dequeue) At(index uint64) (interface{}, error) { + if index >= q.Length() { + return nil, ErrArrayOutOfBounds + } + + id := q.begin + seqid.ID(index) + + value, ok := q.data.Get(id.String()) + if !ok { + return nil, ErrNonExistedValue + } + + return value, nil +} + +// Clear removes all elements from the Dequeue. +func (q *Dequeue) Clear() { + q.begin = 0 + q.end = 0 + q.data = avl.NewTree() +} + +// Length returns the number of elements in the Dequeue. +func (q *Dequeue) Length() uint64 { + return uint64(q.end - q.begin) +} + +// Empty returns true if the Dequeue is empty, false otherwise. +func (q *Dequeue) Empty() bool { + return q.end == q.begin +} diff --git a/examples/gno.land/p/demo/dequeue/dequeue_test.gno b/examples/gno.land/p/demo/dequeue/dequeue_test.gno new file mode 100644 index 00000000000..b5440d7463a --- /dev/null +++ b/examples/gno.land/p/demo/dequeue/dequeue_test.gno @@ -0,0 +1,346 @@ +package dequeue + +import ( + "testing" +) + +func TestPushBack(t *testing.T) { + // Initialize a new queue + queue := NewDequeue() + + // Test pushing an element to the back of the queue + err := queue.PushBack("first") + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + // Test pushing another element to the back of the queue + err = queue.PushBack("second") + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if queue.Length() != 2 { + t.Errorf("Expected length 2, got: %d", queue.Length()) + } +} + +func TestPopBack(t *testing.T) { + // Initialize a new queue + queue := NewDequeue() + + // Add two elements to the queue + queue.PushBack("first") + queue.PushBack("second") + + // Test popping the last element from the queue + value, err := queue.PopBack() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "second" { + t.Errorf("Expected 'second', got: %s", value) + } + + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + // Test popping the last remaining element from the queue + value, err = queue.PopBack() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "first" { + t.Errorf("Expected 'first', got: %s", value) + } + + if queue.Length() != 0 { + t.Errorf("Expected length 0, got: %d", queue.Length()) + } + + // Test popping from an empty queue, should return an error + _, err = queue.PopBack() + if err != ErrEmptyArrayPop { + t.Errorf("Expected error %s, got: %s", ErrEmptyArrayPop, err.Error()) + } +} + +func TestPushFront(t *testing.T) { + // Initialize a new queue + queue := NewDequeue() + + // Test pushing an element to the front of the queue + err := queue.PushFront("first") + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + // Test pushing another element to the front of the queue + err = queue.PushFront("second") + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if queue.Length() != 2 { + t.Errorf("Expected length 2, got: %d", queue.Length()) + } +} + +func TestPopFront(t *testing.T) { + // Initialize a new queue + queue := NewDequeue() + + // Add two elements to the queue + queue.PushBack("first") + queue.PushBack("second") + + // Test popping the front element from the queue + value, err := queue.PopFront() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "first" { + t.Errorf("Expected 'first', got: %s", value) + } + + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + // Test popping the next front element from the queue + value, err = queue.PopFront() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "second" { + t.Errorf("Expected 'second', got: %s", value) + } + + if queue.Length() != 0 { + t.Errorf("Expected length 0, got: %d", queue.Length()) + } + + // Test popping from an empty queue, should return an error + _, err = queue.PopFront() + if err != ErrEmptyArrayPop { + t.Errorf("Expected error %s, got: %s", ErrEmptyArrayPop, err.Error()) + } +} + +func TestFront(t *testing.T) { + // Initialize a new queue + queue := NewDequeue() + + // Add two elements to the queue + queue.PushBack("first") + queue.PushBack("second") + + // Test retrieving the front element without removing it + value, err := queue.Front() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "first" { + t.Errorf("Expected 'first', got: %s", value) + } + + // Remove the front element and test retrieving the new front element + queue.PopFront() + value, err = queue.Front() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "second" { + t.Errorf("Expected 'second', got: %s", value) + } + + // Remove the remaining element and test retrieving the front from an empty queue, should return an error + queue.PopFront() + _, err = queue.Front() + if err != ErrArrayOutOfBounds { + t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) + } +} + +func TestBack(t *testing.T) { + // Initialize a new queue + queue := NewDequeue() + + // Add two elements to the queue + queue.PushBack("first") + queue.PushBack("second") + + // Test retrieving the last element without removing it + value, err := queue.Back() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "second" { + t.Errorf("Expected 'second', got: %s", value) + } + + // Remove the last element and test retrieving the new last element + queue.PopBack() + value, err = queue.Back() + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "first" { + t.Errorf("Expected 'first', got: %s", value) + } + + // Remove the remaining element and test retrieving the last from an empty queue, should return an error + queue.PopBack() + _, err = queue.Back() + if err != ErrArrayOutOfBounds { + t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) + } +} + +func TestAt(t *testing.T) { + // Initialize a new queue + queue := NewDequeue() + + // Add three elements to the queue + queue.PushBack("first") + queue.PushBack("second") + queue.PushBack("third") + + // Test accessing elements at specific indices + value, err := queue.At(0) + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "first" { + t.Errorf("Expected 'first', got: %s", value) + } + + value, err = queue.At(1) + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "second" { + t.Errorf("Expected 'second', got: %s", value) + } + + value, err = queue.At(2) + if err != nil { + t.Errorf("Expected no error, got: %s", err.Error()) + } + + if value != "third" { + t.Errorf("Expected 'third', got: %s", value) + } + + // Test accessing an out-of-bounds index, should return an error + _, err = queue.At(3) + if err != ErrArrayOutOfBounds { + t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) + } +} + +func TestClear(t *testing.T) { + // Initialize a new queue + queue := NewDequeue() + + // Add two elements to the queue + queue.PushBack("first") + queue.PushBack("second") + + // Clear the queue + queue.Clear() + + // Test if the queue is empty after clearing + if queue.Length() != 0 { + t.Errorf("Expected length 0, got: %d", queue.Length()) + } + + if !queue.Empty() { + t.Errorf("Expected queue to be empty") + } + + // Test retrieving the front from an empty queue, should return an error + _, err := queue.Front() + if err != ErrArrayOutOfBounds { + t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) + } + + // Test retrieving the back from an empty queue, should return an error + _, err = queue.Back() + if err != ErrArrayOutOfBounds { + t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) + } +} + +func TestLengthAndEmpty(t *testing.T) { + // Initialize a new queue + queue := NewDequeue() + + // Test initial length and empty status + if queue.Length() != 0 { + t.Errorf("Expected length 0, got: %d", queue.Length()) + } + + if !queue.Empty() { + t.Errorf("Expected queue to be empty") + } + + // Add an element and test length and empty status + queue.PushBack("first") + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + if queue.Empty() { + t.Errorf("Expected queue not to be empty") + } + + // Add another element and test length and empty status + queue.PushBack("second") + if queue.Length() != 2 { + t.Errorf("Expected length 2, got: %d", queue.Length()) + } + + if queue.Empty() { + t.Errorf("Expected queue not to be empty") + } + + // Remove an element and test length and empty status + queue.PopFront() + if queue.Length() != 1 { + t.Errorf("Expected length 1, got: %d", queue.Length()) + } + + if queue.Empty() { + t.Errorf("Expected queue not to be empty") + } + + // Remove the last element and test length and empty status + queue.PopBack() + if queue.Length() != 0 { + t.Errorf("Expected length 0, got: %d", queue.Length()) + } + + if !queue.Empty() { + t.Errorf("Expected queue to be empty") + } +} diff --git a/examples/gno.land/p/demo/dequeue/errors.gno b/examples/gno.land/p/demo/dequeue/errors.gno new file mode 100644 index 00000000000..a777e38234b --- /dev/null +++ b/examples/gno.land/p/demo/dequeue/errors.gno @@ -0,0 +1,14 @@ +package dequeue + +import ( + "errors" +) + +// Error definitions for various edge cases +var ( + ErrResourceError = errors.New("resource error: queue is full") + ErrEmptyArrayPop = errors.New("empty array pop: queue is empty") + ErrArrayOutOfBounds = errors.New("array out of bounds") + + ErrNonExistedValue = errors.New("non-existed value") +) diff --git a/examples/gno.land/p/demo/dequeue/gno.mod b/examples/gno.land/p/demo/dequeue/gno.mod new file mode 100644 index 00000000000..1ab255ba8a6 --- /dev/null +++ b/examples/gno.land/p/demo/dequeue/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/demo/dequeue + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest +) From df770d6ca4c9e1935f8f32f588a02078f5021ad1 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Tue, 25 Jun 2024 15:44:51 +0700 Subject: [PATCH 07/26] remove queue --- examples/gno.land/p/demo/queue/errors.gno | 14 - examples/gno.land/p/demo/queue/gno.mod | 6 - examples/gno.land/p/demo/queue/queue.gno | 154 -------- examples/gno.land/p/demo/queue/queue_test.gno | 346 ------------------ 4 files changed, 520 deletions(-) delete mode 100644 examples/gno.land/p/demo/queue/errors.gno delete mode 100644 examples/gno.land/p/demo/queue/gno.mod delete mode 100644 examples/gno.land/p/demo/queue/queue.gno delete mode 100644 examples/gno.land/p/demo/queue/queue_test.gno diff --git a/examples/gno.land/p/demo/queue/errors.gno b/examples/gno.land/p/demo/queue/errors.gno deleted file mode 100644 index 195f9647ea5..00000000000 --- a/examples/gno.land/p/demo/queue/errors.gno +++ /dev/null @@ -1,14 +0,0 @@ -package queue - -import ( - "errors" -) - -// Error definitions for various edge cases -var ( - ErrResourceError = errors.New("resource error: queue is full") - ErrEmptyArrayPop = errors.New("empty array pop: queue is empty") - ErrArrayOutOfBounds = errors.New("array out of bounds") - - ErrNonExistedValue = errors.New("non-existed value") -) diff --git a/examples/gno.land/p/demo/queue/gno.mod b/examples/gno.land/p/demo/queue/gno.mod deleted file mode 100644 index 21271054ba8..00000000000 --- a/examples/gno.land/p/demo/queue/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/demo/queue - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/seqid v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/queue/queue.gno b/examples/gno.land/p/demo/queue/queue.gno deleted file mode 100644 index 97bd2406806..00000000000 --- a/examples/gno.land/p/demo/queue/queue.gno +++ /dev/null @@ -1,154 +0,0 @@ -package queue - -import ( - "gno.land/p/demo/avl" - "gno.land/p/demo/seqid" -) - -// Queue is a queue data structure that stores elements of any type. -type Queue struct { - begin seqid.ID // Index of the front element - end seqid.ID // Index after the last element - data *avl.Tree // Storage for queue elements -} - -// NewQueue creates and initializes a new queue. -func NewQueue() *Queue { - return &Queue{ - begin: seqid.ID(0), - end: seqid.ID(0), - data: avl.NewTree(), - } -} - -// PushBack adds an element to the end of the queue. -// Returns an error if the queue is full. -func (q *Queue) PushBack(value interface{}) error { - if q.end+1 == q.begin { - return ErrResourceError - } - - q.data.Set(q.end.String(), value) - - q.end++ - - return nil -} - -// PopBack removes and returns the element at the end of the queue. -// Returns an error if the queue is empty. -func (q *Queue) PopBack() (interface{}, error) { - if q.begin == q.end { - return nil, ErrEmptyArrayPop - } - - q.end-- - value, ok := q.data.Get(q.end.String()) - if !ok { - return nil, ErrNonExistedValue - } - - q.data.Remove(q.end.String()) - - return value, nil -} - -// PushFront adds an element to the front of the queue. -// Returns an error if the queue is full. -func (q *Queue) PushFront(value interface{}) error { - if q.begin-1 == q.end { - return ErrResourceError - } - - q.begin-- - q.data.Set(q.begin.String(), value) - - return nil -} - -// PopFront removes and returns the element at the front of the queue. -// Returns an error if the queue is empty. -func (q *Queue) PopFront() (interface{}, error) { - if q.begin == q.end { - return nil, ErrEmptyArrayPop - } - - value, ok := q.data.Get(q.begin.String()) - if !ok { - return nil, ErrNonExistedValue - } - - q.data.Remove(q.begin.String()) - - q.begin++ - - return value, nil -} - -// Front returns the element at the front of the queue without removing it. -// Returns an error if the queue is empty. -func (q *Queue) Front() (interface{}, error) { - if q.Empty() { - return nil, ErrArrayOutOfBounds - } - - id := q.begin - - value, ok := q.data.Get(id.String()) - if !ok { - return nil, ErrNonExistedValue - } - - return value, nil -} - -// Back returns the element at the end of the queue without removing it. -// Returns an error if the queue is empty. -func (q *Queue) Back() (interface{}, error) { - if q.Empty() { - return nil, ErrArrayOutOfBounds - } - - id := q.end - 1 - - value, ok := q.data.Get(id.String()) - if !ok { - return nil, ErrNonExistedValue - } - - return value, nil -} - -// At returns the element at the specified index in the queue. -// Returns an error if the index is out of bounds. -func (q *Queue) At(index uint64) (interface{}, error) { - if index >= q.Length() { - return nil, ErrArrayOutOfBounds - } - - id := q.begin + seqid.ID(index) - - value, ok := q.data.Get(id.String()) - if !ok { - return nil, ErrNonExistedValue - } - - return value, nil -} - -// Clear removes all elements from the queue. -func (q *Queue) Clear() { - q.begin = 0 - q.end = 0 - q.data = avl.NewTree() -} - -// Length returns the number of elements in the queue. -func (q *Queue) Length() uint64 { - return uint64(q.end - q.begin) -} - -// Empty returns true if the queue is empty, false otherwise. -func (q *Queue) Empty() bool { - return q.end == q.begin -} diff --git a/examples/gno.land/p/demo/queue/queue_test.gno b/examples/gno.land/p/demo/queue/queue_test.gno deleted file mode 100644 index eb4cd1c154d..00000000000 --- a/examples/gno.land/p/demo/queue/queue_test.gno +++ /dev/null @@ -1,346 +0,0 @@ -package queue - -import ( - "testing" -) - -func TestPushBack(t *testing.T) { - // Initialize a new queue - queue := NewQueue() - - // Test pushing an element to the back of the queue - err := queue.PushBack("first") - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if queue.Length() != 1 { - t.Errorf("Expected length 1, got: %d", queue.Length()) - } - - // Test pushing another element to the back of the queue - err = queue.PushBack("second") - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if queue.Length() != 2 { - t.Errorf("Expected length 2, got: %d", queue.Length()) - } -} - -func TestPopBack(t *testing.T) { - // Initialize a new queue - queue := NewQueue() - - // Add two elements to the queue - queue.PushBack("first") - queue.PushBack("second") - - // Test popping the last element from the queue - value, err := queue.PopBack() - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if value != "second" { - t.Errorf("Expected 'second', got: %s", value) - } - - if queue.Length() != 1 { - t.Errorf("Expected length 1, got: %d", queue.Length()) - } - - // Test popping the last remaining element from the queue - value, err = queue.PopBack() - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if value != "first" { - t.Errorf("Expected 'first', got: %s", value) - } - - if queue.Length() != 0 { - t.Errorf("Expected length 0, got: %d", queue.Length()) - } - - // Test popping from an empty queue, should return an error - _, err = queue.PopBack() - if err != ErrEmptyArrayPop { - t.Errorf("Expected error %s, got: %s", ErrEmptyArrayPop, err.Error()) - } -} - -func TestPushFront(t *testing.T) { - // Initialize a new queue - queue := NewQueue() - - // Test pushing an element to the front of the queue - err := queue.PushFront("first") - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if queue.Length() != 1 { - t.Errorf("Expected length 1, got: %d", queue.Length()) - } - - // Test pushing another element to the front of the queue - err = queue.PushFront("second") - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if queue.Length() != 2 { - t.Errorf("Expected length 2, got: %d", queue.Length()) - } -} - -func TestPopFront(t *testing.T) { - // Initialize a new queue - queue := NewQueue() - - // Add two elements to the queue - queue.PushBack("first") - queue.PushBack("second") - - // Test popping the front element from the queue - value, err := queue.PopFront() - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if value != "first" { - t.Errorf("Expected 'first', got: %s", value) - } - - if queue.Length() != 1 { - t.Errorf("Expected length 1, got: %d", queue.Length()) - } - - // Test popping the next front element from the queue - value, err = queue.PopFront() - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if value != "second" { - t.Errorf("Expected 'second', got: %s", value) - } - - if queue.Length() != 0 { - t.Errorf("Expected length 0, got: %d", queue.Length()) - } - - // Test popping from an empty queue, should return an error - _, err = queue.PopFront() - if err != ErrEmptyArrayPop { - t.Errorf("Expected error %s, got: %s", ErrEmptyArrayPop, err.Error()) - } -} - -func TestFront(t *testing.T) { - // Initialize a new queue - queue := NewQueue() - - // Add two elements to the queue - queue.PushBack("first") - queue.PushBack("second") - - // Test retrieving the front element without removing it - value, err := queue.Front() - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if value != "first" { - t.Errorf("Expected 'first', got: %s", value) - } - - // Remove the front element and test retrieving the new front element - queue.PopFront() - value, err = queue.Front() - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if value != "second" { - t.Errorf("Expected 'second', got: %s", value) - } - - // Remove the remaining element and test retrieving the front from an empty queue, should return an error - queue.PopFront() - _, err = queue.Front() - if err != ErrArrayOutOfBounds { - t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) - } -} - -func TestBack(t *testing.T) { - // Initialize a new queue - queue := NewQueue() - - // Add two elements to the queue - queue.PushBack("first") - queue.PushBack("second") - - // Test retrieving the last element without removing it - value, err := queue.Back() - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if value != "second" { - t.Errorf("Expected 'second', got: %s", value) - } - - // Remove the last element and test retrieving the new last element - queue.PopBack() - value, err = queue.Back() - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if value != "first" { - t.Errorf("Expected 'first', got: %s", value) - } - - // Remove the remaining element and test retrieving the last from an empty queue, should return an error - queue.PopBack() - _, err = queue.Back() - if err != ErrArrayOutOfBounds { - t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) - } -} - -func TestAt(t *testing.T) { - // Initialize a new queue - queue := NewQueue() - - // Add three elements to the queue - queue.PushBack("first") - queue.PushBack("second") - queue.PushBack("third") - - // Test accessing elements at specific indices - value, err := queue.At(0) - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if value != "first" { - t.Errorf("Expected 'first', got: %s", value) - } - - value, err = queue.At(1) - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if value != "second" { - t.Errorf("Expected 'second', got: %s", value) - } - - value, err = queue.At(2) - if err != nil { - t.Errorf("Expected no error, got: %s", err.Error()) - } - - if value != "third" { - t.Errorf("Expected 'third', got: %s", value) - } - - // Test accessing an out-of-bounds index, should return an error - _, err = queue.At(3) - if err != ErrArrayOutOfBounds { - t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) - } -} - -func TestClear(t *testing.T) { - // Initialize a new queue - queue := NewQueue() - - // Add two elements to the queue - queue.PushBack("first") - queue.PushBack("second") - - // Clear the queue - queue.Clear() - - // Test if the queue is empty after clearing - if queue.Length() != 0 { - t.Errorf("Expected length 0, got: %d", queue.Length()) - } - - if !queue.Empty() { - t.Errorf("Expected queue to be empty") - } - - // Test retrieving the front from an empty queue, should return an error - _, err := queue.Front() - if err != ErrArrayOutOfBounds { - t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) - } - - // Test retrieving the back from an empty queue, should return an error - _, err = queue.Back() - if err != ErrArrayOutOfBounds { - t.Errorf("Expected error %s, got: %s", ErrArrayOutOfBounds, err.Error()) - } -} - -func TestLengthAndEmpty(t *testing.T) { - // Initialize a new queue - queue := NewQueue() - - // Test initial length and empty status - if queue.Length() != 0 { - t.Errorf("Expected length 0, got: %d", queue.Length()) - } - - if !queue.Empty() { - t.Errorf("Expected queue to be empty") - } - - // Add an element and test length and empty status - queue.PushBack("first") - if queue.Length() != 1 { - t.Errorf("Expected length 1, got: %d", queue.Length()) - } - - if queue.Empty() { - t.Errorf("Expected queue not to be empty") - } - - // Add another element and test length and empty status - queue.PushBack("second") - if queue.Length() != 2 { - t.Errorf("Expected length 2, got: %d", queue.Length()) - } - - if queue.Empty() { - t.Errorf("Expected queue not to be empty") - } - - // Remove an element and test length and empty status - queue.PopFront() - if queue.Length() != 1 { - t.Errorf("Expected length 1, got: %d", queue.Length()) - } - - if queue.Empty() { - t.Errorf("Expected queue not to be empty") - } - - // Remove the last element and test length and empty status - queue.PopBack() - if queue.Length() != 0 { - t.Errorf("Expected length 0, got: %d", queue.Length()) - } - - if !queue.Empty() { - t.Errorf("Expected queue to be empty") - } -} From 49d32e3e6f6144c2997093344a2da99287a689de Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Thu, 4 Jul 2024 09:22:03 +0700 Subject: [PATCH 08/26] rename NewDequeue to New --- examples/gno.land/p/demo/dequeue/dequeue.gno | 4 ++-- .../gno.land/p/demo/dequeue/dequeue_test.gno | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/gno.land/p/demo/dequeue/dequeue.gno b/examples/gno.land/p/demo/dequeue/dequeue.gno index 58793abaefe..c2db00d79a8 100644 --- a/examples/gno.land/p/demo/dequeue/dequeue.gno +++ b/examples/gno.land/p/demo/dequeue/dequeue.gno @@ -12,8 +12,8 @@ type Dequeue struct { data *avl.Tree // Storage for Dequeue elements } -// NewDequeue creates and initializes a new Dequeue. -func NewDequeue() *Dequeue { +// New creates and initializes a new Dequeue. +func New() *Dequeue { return &Dequeue{ begin: seqid.ID(0), end: seqid.ID(0), diff --git a/examples/gno.land/p/demo/dequeue/dequeue_test.gno b/examples/gno.land/p/demo/dequeue/dequeue_test.gno index b5440d7463a..cba3ab20943 100644 --- a/examples/gno.land/p/demo/dequeue/dequeue_test.gno +++ b/examples/gno.land/p/demo/dequeue/dequeue_test.gno @@ -6,7 +6,7 @@ import ( func TestPushBack(t *testing.T) { // Initialize a new queue - queue := NewDequeue() + queue := New() // Test pushing an element to the back of the queue err := queue.PushBack("first") @@ -31,7 +31,7 @@ func TestPushBack(t *testing.T) { func TestPopBack(t *testing.T) { // Initialize a new queue - queue := NewDequeue() + queue := New() // Add two elements to the queue queue.PushBack("first") @@ -74,7 +74,7 @@ func TestPopBack(t *testing.T) { func TestPushFront(t *testing.T) { // Initialize a new queue - queue := NewDequeue() + queue := New() // Test pushing an element to the front of the queue err := queue.PushFront("first") @@ -99,7 +99,7 @@ func TestPushFront(t *testing.T) { func TestPopFront(t *testing.T) { // Initialize a new queue - queue := NewDequeue() + queue := New() // Add two elements to the queue queue.PushBack("first") @@ -142,7 +142,7 @@ func TestPopFront(t *testing.T) { func TestFront(t *testing.T) { // Initialize a new queue - queue := NewDequeue() + queue := New() // Add two elements to the queue queue.PushBack("first") @@ -179,7 +179,7 @@ func TestFront(t *testing.T) { func TestBack(t *testing.T) { // Initialize a new queue - queue := NewDequeue() + queue := New() // Add two elements to the queue queue.PushBack("first") @@ -216,7 +216,7 @@ func TestBack(t *testing.T) { func TestAt(t *testing.T) { // Initialize a new queue - queue := NewDequeue() + queue := New() // Add three elements to the queue queue.PushBack("first") @@ -260,7 +260,7 @@ func TestAt(t *testing.T) { func TestClear(t *testing.T) { // Initialize a new queue - queue := NewDequeue() + queue := New() // Add two elements to the queue queue.PushBack("first") @@ -293,7 +293,7 @@ func TestClear(t *testing.T) { func TestLengthAndEmpty(t *testing.T) { // Initialize a new queue - queue := NewDequeue() + queue := New() // Test initial length and empty status if queue.Length() != 0 { From 7c78d84da35e5b80fcbebdd809a9749ee07fa2b8 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Thu, 4 Jul 2024 14:28:50 +0700 Subject: [PATCH 09/26] fix CI --- examples/gno.land/r/gnoland/home/home_filetest.gno | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno index 919f8dd4fbc..2f2ac26f2fe 100644 --- a/examples/gno.land/r/gnoland/home/home_filetest.gno +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -133,7 +133,8 @@ func main() { // - [r/demo/microblog](r/demo/microblog) // - [r/demo/nft](r/demo/nft) // - [r/demo/types](r/demo/types) -// - [r/demo/art](r/demo/art) +// - [r/demo/art/gnoface](r/demo/art/gnoface) +// - [r/demo/art/millipede](r/demo/art/millipede) // - [r/demo/groups](r/demo/groups) // - ... // From 509e1898aeb0c441138de69cedc27e3841e9ae92 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Sun, 14 Jul 2024 20:07:19 +0700 Subject: [PATCH 10/26] add OrderBook package implement dequeue --- examples/gno.land/p/demo/orderbook/errors.gno | 11 ++ examples/gno.land/p/demo/orderbook/gno.mod | 3 + .../gno.land/p/demo/orderbook/orderbook.gno | 139 ++++++++++++++++++ .../p/demo/orderbook/orderbook_test.gno | 137 +++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 examples/gno.land/p/demo/orderbook/errors.gno create mode 100644 examples/gno.land/p/demo/orderbook/gno.mod create mode 100644 examples/gno.land/p/demo/orderbook/orderbook.gno create mode 100644 examples/gno.land/p/demo/orderbook/orderbook_test.gno diff --git a/examples/gno.land/p/demo/orderbook/errors.gno b/examples/gno.land/p/demo/orderbook/errors.gno new file mode 100644 index 00000000000..0c984395fd6 --- /dev/null +++ b/examples/gno.land/p/demo/orderbook/errors.gno @@ -0,0 +1,11 @@ +// errors.go +package orderbook + +import "errors" + +var ( + ErrFailedToAddOrder = errors.New("failed to add order") + ErrFailedToRemoveOrder = errors.New("failed to remove order") + ErrFailedToPeekOrder = errors.New("failed to peek order") + ErrUnknownOrderType = errors.New("unknown order type") +) diff --git a/examples/gno.land/p/demo/orderbook/gno.mod b/examples/gno.land/p/demo/orderbook/gno.mod new file mode 100644 index 00000000000..8a6ff6b727e --- /dev/null +++ b/examples/gno.land/p/demo/orderbook/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/demo/orderbook + +require gno.land/p/demo/dequeue v0.0.0-latest diff --git a/examples/gno.land/p/demo/orderbook/orderbook.gno b/examples/gno.land/p/demo/orderbook/orderbook.gno new file mode 100644 index 00000000000..d24be4e05db --- /dev/null +++ b/examples/gno.land/p/demo/orderbook/orderbook.gno @@ -0,0 +1,139 @@ +package orderbook + +import ( + "fmt" + + "gno.land/p/demo/dequeue" +) + +type OrderType int + +const ( + Buy OrderType = iota + Sell +) + +type Order struct { + ID string + Type OrderType + Quantity int + Price float64 +} + +type OrderBook struct { + buyOrders *dequeue.Dequeue + sellOrders *dequeue.Dequeue +} + +// NewOrderBook initializes a new order book +func NewOrderBook() *OrderBook { + return &OrderBook{ + buyOrders: dequeue.New(), + sellOrders: dequeue.New(), + } +} + +// AddOrder adds a new order to the order book +func (ob *OrderBook) AddOrder(order Order) error { + switch order.Type { + case Buy: + err := ob.buyOrders.PushBack(order) + if err != nil { + return ErrFailedToAddOrder + } + case Sell: + err := ob.sellOrders.PushBack(order) + if err != nil { + return ErrFailedToAddOrder + } + default: + return ErrUnknownOrderType + } + return nil +} + +// RemoveOrder removes the first order from the order book +func (ob *OrderBook) RemoveOrder(orderType OrderType) error { + var err error + + switch orderType { + case Buy: + _, err = ob.buyOrders.PopFront() + if err != nil { + return ErrFailedToRemoveOrder + } + case Sell: + _, err = ob.sellOrders.PopFront() + if err != nil { + return ErrFailedToRemoveOrder + } + default: + return ErrUnknownOrderType + } + + return nil +} + +// PeekOrder returns the first order in the order book without removing it +func (ob *OrderBook) PeekOrder(orderType OrderType) (Order, error) { + switch orderType { + case Buy: + order, err := ob.buyOrders.Front() + if err != nil { + return Order{}, ErrFailedToPeekOrder + } + return order.(Order), nil + case Sell: + order, err := ob.sellOrders.Front() + if err != nil { + return Order{}, ErrFailedToPeekOrder + } + return order.(Order), nil + default: + return Order{}, ErrUnknownOrderType + } +} + +// GetOrderCount returns the number of orders in the order book +func (ob *OrderBook) GetOrderCount(orderType OrderType) (uint64, error) { + switch orderType { + case Buy: + return ob.buyOrders.Length(), nil + case Sell: + return ob.sellOrders.Length(), nil + default: + return 0, ErrUnknownOrderType + } +} + +// Render returns a string representation of the order book with up to n orders per type +func (ob *OrderBook) Render(n int) string { + var output string + + // buy orders + output += "Buy Orders:\n" + buyLength := ob.buyOrders.Length() + for i := uint64(0); i < buyLength && i < uint64(n); i++ { + value, err := ob.buyOrders.At(i) + if err != nil { + continue + } + order := value.(Order) + output += fmt.Sprintf("ID: %s, Type: Buy, Quantity: %d, Price: %.2f\n", order.ID, order.Quantity, order.Price) + } + output += "\n" + + // sell orders + output += "Sell Orders:\n" + sellLength := ob.sellOrders.Length() + for i := uint64(0); i < sellLength && i < uint64(n); i++ { + value, err := ob.sellOrders.At(i) + if err != nil { + continue + } + order := value.(Order) + output += fmt.Sprintf("ID: %s, Type: Sell, Quantity: %d, Price: %.2f\n", order.ID, order.Quantity, order.Price) + } + + return output +} diff --git a/examples/gno.land/p/demo/orderbook/orderbook_test.gno b/examples/gno.land/p/demo/orderbook/orderbook_test.gno new file mode 100644 index 00000000000..0afc29ad67b --- /dev/null +++ b/examples/gno.land/p/demo/orderbook/orderbook_test.gno @@ -0,0 +1,137 @@ +package orderbook + +import ( + "testing" + + "gno.land/p/demo/uassert" +) + +func TestOrderBook_AddOrder(t *testing.T) { + ob := NewOrderBook() + + order := Order{ + ID: "1", + Type: Buy, + Quantity: 10, + Price: 100.0, + } + + err := ob.AddOrder(order) + uassert.NoError(t, err, "AddOrder should succeed") + + // Test adding a sell order + order.Type = Sell + err = ob.AddOrder(order) + uassert.NoError(t, err, "AddOrder should succeed for sell order") +} + +func TestOrderBook_RemoveOrder(t *testing.T) { + ob := NewOrderBook() + + order := Order{ + ID: "1", + Type: Buy, + Quantity: 10, + Price: 100.0, + } + + err := ob.AddOrder(order) + uassert.NoError(t, err, "AddOrder should succeed") + + // Test removing a buy order + err = ob.RemoveOrder(Buy) + uassert.NoError(t, err, "RemoveOrder should succeed for buy order") + + // Test removing a sell order + err = ob.RemoveOrder(Sell) + uassert.Error(t, err, "RemoveOrder should fail for empty sell orders") +} + +func TestOrderBook_PeekOrder(t *testing.T) { + ob := NewOrderBook() + + order := Order{ + ID: "1", + Type: Buy, + Quantity: 10, + Price: 100.0, + } + + err := ob.AddOrder(order) + uassert.NoError(t, err, "AddOrder should succeed") + + // Test peeking a buy order + peekedOrder, err := ob.PeekOrder(Buy) + uassert.NoError(t, err, "PeekOrder should succeed for buy order") + uassert.NotEmpty(t, peekedOrder.ID, "Peeked order should not be nil") + + // Test peeking a sell order + peekedOrder, err = ob.PeekOrder(Sell) + uassert.Error(t, err, "PeekOrder should fail for empty sell orders") + uassert.Empty(t, peekedOrder.ID, "Peeked order should be nil for empty sell orders") +} + +func TestOrderBook_GetOrderCount(t *testing.T) { + ob := NewOrderBook() + + order := Order{ + ID: "1", + Type: Buy, + Quantity: 10, + Price: 100.0, + } + + err := ob.AddOrder(order) + uassert.NoError(t, err, "AddOrder should succeed") + + // Test getting order count for buy orders + count, err := ob.GetOrderCount(Buy) + uassert.NoError(t, err, "GetOrderCount should succeed for buy orders") + uassert.Equal(t, uint64(1), count, "Buy order count should be 1") + + // Test getting order count for sell orders + count, err = ob.GetOrderCount(Sell) + uassert.NoError(t, err, "GetOrderCount should succeed for sell orders") + uassert.Equal(t, uint64(0), count, "Sell order count should be 0") +} + +func TestOrderBook_Render(t *testing.T) { + ob := NewOrderBook() + + order1 := Order{ + ID: "1", + Type: Buy, + Quantity: 10, + Price: 100.0, + } + order2 := Order{ + ID: "2", + Type: Buy, + Quantity: 15, + Price: 95.0, + } + order3 := Order{ + ID: "3", + Type: Sell, + Quantity: 5, + Price: 110.0, + } + order4 := Order{ + ID: "4", + Type: Sell, + Quantity: 8, + Price: 115.0, + } + + // Add orders to the order book + ob.AddOrder(order1) + ob.AddOrder(order2) + ob.AddOrder(order3) + ob.AddOrder(order4) + + expected := "Buy Orders:\nID: 1, Type: Buy, Quantity: 10, Price: 100.00\nID: 2, Type: Buy, Quantity: 15, Price: 95.00\n\nSell Orders:\nID: 3, Type: Sell, Quantity: 5, Price: 110.00\nID: 4, Type: Sell, Quantity: 8, Price: 115.00\n" + + result := ob.Render(3) // Limit to 3 orders per type + + uassert.Equal(t, expected, result, "Render() result does not match expected") +} From 9a423e93147ab9163a3ce118bd23c5697f549462 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Sun, 14 Jul 2024 20:09:01 +0700 Subject: [PATCH 11/26] make tidy --- examples/gno.land/p/demo/orderbook/gno.mod | 6 +++++- examples/gno.land/p/demo/orderbook/orderbook.gno | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/p/demo/orderbook/gno.mod b/examples/gno.land/p/demo/orderbook/gno.mod index 8a6ff6b727e..e335c64a54a 100644 --- a/examples/gno.land/p/demo/orderbook/gno.mod +++ b/examples/gno.land/p/demo/orderbook/gno.mod @@ -1,3 +1,7 @@ module gno.land/r/demo/orderbook -require gno.land/p/demo/dequeue v0.0.0-latest +require ( + gno.land/p/demo/dequeue v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/orderbook/orderbook.gno b/examples/gno.land/p/demo/orderbook/orderbook.gno index d24be4e05db..a1c367cf7df 100644 --- a/examples/gno.land/p/demo/orderbook/orderbook.gno +++ b/examples/gno.land/p/demo/orderbook/orderbook.gno @@ -4,6 +4,7 @@ import ( "fmt" "gno.land/p/demo/dequeue" + "gno.land/p/demo/ufmt" ) type OrderType int @@ -119,7 +120,7 @@ func (ob *OrderBook) Render(n int) string { continue } order := value.(Order) - output += fmt.Sprintf("ID: %s, Type: Buy, Quantity: %d, Price: %.2f\n", order.ID, order.Quantity, order.Price) + output += ufmt.Sprintf("ID: %s, Type: Buy, Quantity: %d, Price: %.2f\n", order.ID, order.Quantity, order.Price) } output += "\n" @@ -132,7 +133,7 @@ func (ob *OrderBook) Render(n int) string { continue } order := value.(Order) - output += fmt.Sprintf("ID: %s, Type: Sell, Quantity: %d, Price: %.2f\n", order.ID, order.Quantity, order.Price) + output += ufmt.Sprintf("ID: %s, Type: Sell, Quantity: %d, Price: %.2f\n", order.ID, order.Quantity, order.Price) } return output From c758b91fba878d576f9adb8bfc478bddd1049b82 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Sun, 14 Jul 2024 20:12:11 +0700 Subject: [PATCH 12/26] fix --- examples/gno.land/p/demo/orderbook/errors.gno | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/gno.land/p/demo/orderbook/errors.gno b/examples/gno.land/p/demo/orderbook/errors.gno index 0c984395fd6..3d8679522b8 100644 --- a/examples/gno.land/p/demo/orderbook/errors.gno +++ b/examples/gno.land/p/demo/orderbook/errors.gno @@ -1,4 +1,3 @@ -// errors.go package orderbook import "errors" From 03ecaa9b10e1e60de1294d2056fb2debfae5e12a Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Sun, 14 Jul 2024 20:13:39 +0700 Subject: [PATCH 13/26] fix --- examples/gno.land/p/demo/orderbook/orderbook.gno | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/gno.land/p/demo/orderbook/orderbook.gno b/examples/gno.land/p/demo/orderbook/orderbook.gno index a1c367cf7df..6ce51f18507 100644 --- a/examples/gno.land/p/demo/orderbook/orderbook.gno +++ b/examples/gno.land/p/demo/orderbook/orderbook.gno @@ -1,8 +1,6 @@ package orderbook import ( - "fmt" - "gno.land/p/demo/dequeue" "gno.land/p/demo/ufmt" ) From de3b51c28d512a4023d4915eb76f05e8c580183a Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Sun, 14 Jul 2024 20:58:50 +0700 Subject: [PATCH 14/26] fixup --- .../gno.land/p/demo/orderbook/orderbook.gno | 14 +++++----- .../p/demo/orderbook/orderbook_test.gno | 27 ++++++++++++------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/examples/gno.land/p/demo/orderbook/orderbook.gno b/examples/gno.land/p/demo/orderbook/orderbook.gno index 6ce51f18507..a9aaef55f68 100644 --- a/examples/gno.land/p/demo/orderbook/orderbook.gno +++ b/examples/gno.land/p/demo/orderbook/orderbook.gno @@ -12,13 +12,15 @@ const ( Sell ) +// Order represents a single order with ID, type (Buy or Sell), quantity, and price type Order struct { - ID string - Type OrderType - Quantity int - Price float64 + ID string // ID of the order + Type OrderType // Type of the order (Buy or Sell) + Quantity int // Quantity of the order + Price uint64 // Price of the order (temporary uint64 due to lack of float support) } +// OrderBook represents a collection of buy and sell orders type OrderBook struct { buyOrders *dequeue.Dequeue sellOrders *dequeue.Dequeue @@ -118,7 +120,7 @@ func (ob *OrderBook) Render(n int) string { continue } order := value.(Order) - output += ufmt.Sprintf("ID: %s, Type: Buy, Quantity: %d, Price: %.2f\n", order.ID, order.Quantity, order.Price) + output += ufmt.Sprintf("ID: %s, Quantity: %d, Price: %d\n", order.ID, order.Quantity, order.Price) } output += "\n" @@ -131,7 +133,7 @@ func (ob *OrderBook) Render(n int) string { continue } order := value.(Order) - output += ufmt.Sprintf("ID: %s, Type: Sell, Quantity: %d, Price: %.2f\n", order.ID, order.Quantity, order.Price) + output += ufmt.Sprintf("ID: %s, Quantity: %d, Price: %d\n", order.ID, order.Quantity, order.Price) } return output diff --git a/examples/gno.land/p/demo/orderbook/orderbook_test.gno b/examples/gno.land/p/demo/orderbook/orderbook_test.gno index 0afc29ad67b..bbae1676fad 100644 --- a/examples/gno.land/p/demo/orderbook/orderbook_test.gno +++ b/examples/gno.land/p/demo/orderbook/orderbook_test.gno @@ -13,7 +13,7 @@ func TestOrderBook_AddOrder(t *testing.T) { ID: "1", Type: Buy, Quantity: 10, - Price: 100.0, + Price: 100, } err := ob.AddOrder(order) @@ -32,7 +32,7 @@ func TestOrderBook_RemoveOrder(t *testing.T) { ID: "1", Type: Buy, Quantity: 10, - Price: 100.0, + Price: 100, } err := ob.AddOrder(order) @@ -54,7 +54,7 @@ func TestOrderBook_PeekOrder(t *testing.T) { ID: "1", Type: Buy, Quantity: 10, - Price: 100.0, + Price: 100, } err := ob.AddOrder(order) @@ -78,7 +78,7 @@ func TestOrderBook_GetOrderCount(t *testing.T) { ID: "1", Type: Buy, Quantity: 10, - Price: 100.0, + Price: 100, } err := ob.AddOrder(order) @@ -102,25 +102,25 @@ func TestOrderBook_Render(t *testing.T) { ID: "1", Type: Buy, Quantity: 10, - Price: 100.0, + Price: 100, } order2 := Order{ ID: "2", Type: Buy, Quantity: 15, - Price: 95.0, + Price: 95, } order3 := Order{ ID: "3", Type: Sell, Quantity: 5, - Price: 110.0, + Price: 110, } order4 := Order{ ID: "4", Type: Sell, Quantity: 8, - Price: 115.0, + Price: 115, } // Add orders to the order book @@ -129,9 +129,16 @@ func TestOrderBook_Render(t *testing.T) { ob.AddOrder(order3) ob.AddOrder(order4) - expected := "Buy Orders:\nID: 1, Type: Buy, Quantity: 10, Price: 100.00\nID: 2, Type: Buy, Quantity: 15, Price: 95.00\n\nSell Orders:\nID: 3, Type: Sell, Quantity: 5, Price: 110.00\nID: 4, Type: Sell, Quantity: 8, Price: 115.00\n" + expected := "Buy Orders:\nID: 1, Quantity: 10, Price: 100\n\nSell Orders:\nID: 3, Quantity: 5, Price: 110\n" + result := ob.Render(1) - result := ob.Render(3) // Limit to 3 orders per type + uassert.Equal(t, expected, result, "Render() result does not match expected") + + expected = "Buy Orders:\nID: 1, Quantity: 10, Price: 100\nID: 2, Quantity: 15, Price: 95\n\nSell Orders:\nID: 3, Quantity: 5, Price: 110\nID: 4, Quantity: 8, Price: 115\n" + result = ob.Render(2) + + uassert.Equal(t, expected, result, "Render() result does not match expected") + result = ob.Render(3) uassert.Equal(t, expected, result, "Render() result does not match expected") } From fa510b90306115d83351e7bc8471b77298fc1354 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Sun, 14 Jul 2024 21:15:12 +0700 Subject: [PATCH 15/26] fixup --- .../gno.land/p/demo/orderbook/orderbook.gno | 29 ++++++++++++------- .../p/demo/orderbook/orderbook_test.gno | 10 +++---- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/examples/gno.land/p/demo/orderbook/orderbook.gno b/examples/gno.land/p/demo/orderbook/orderbook.gno index a9aaef55f68..85b3e111a9b 100644 --- a/examples/gno.land/p/demo/orderbook/orderbook.gno +++ b/examples/gno.land/p/demo/orderbook/orderbook.gno @@ -20,22 +20,31 @@ type Order struct { Price uint64 // Price of the order (temporary uint64 due to lack of float support) } -// OrderBook represents a collection of buy and sell orders -type OrderBook struct { +// OrderBook defines the interface for an order book +type OrderBook interface { + AddOrder(order Order) error + RemoveOrder(orderType OrderType) error + PeekOrder(orderType OrderType) (Order, error) + GetOrderCount(orderType OrderType) (uint64, error) + Render(n int) string +} + +// priorityOrderBook represents an order book prioritized by order type (Buy or Sell) +type priorityOrderBook struct { buyOrders *dequeue.Dequeue sellOrders *dequeue.Dequeue } -// NewOrderBook initializes a new order book -func NewOrderBook() *OrderBook { - return &OrderBook{ +// NewOrderBook initializes a new priority order book +func NewPriorityOrderBook() OrderBook { + return &priorityOrderBook{ buyOrders: dequeue.New(), sellOrders: dequeue.New(), } } // AddOrder adds a new order to the order book -func (ob *OrderBook) AddOrder(order Order) error { +func (ob *priorityOrderBook) AddOrder(order Order) error { switch order.Type { case Buy: err := ob.buyOrders.PushBack(order) @@ -54,7 +63,7 @@ func (ob *OrderBook) AddOrder(order Order) error { } // RemoveOrder removes the first order from the order book -func (ob *OrderBook) RemoveOrder(orderType OrderType) error { +func (ob *priorityOrderBook) RemoveOrder(orderType OrderType) error { var err error switch orderType { @@ -76,7 +85,7 @@ func (ob *OrderBook) RemoveOrder(orderType OrderType) error { } // PeekOrder returns the first order in the order book without removing it -func (ob *OrderBook) PeekOrder(orderType OrderType) (Order, error) { +func (ob *priorityOrderBook) PeekOrder(orderType OrderType) (Order, error) { switch orderType { case Buy: order, err := ob.buyOrders.Front() @@ -96,7 +105,7 @@ func (ob *OrderBook) PeekOrder(orderType OrderType) (Order, error) { } // GetOrderCount returns the number of orders in the order book -func (ob *OrderBook) GetOrderCount(orderType OrderType) (uint64, error) { +func (ob *priorityOrderBook) GetOrderCount(orderType OrderType) (uint64, error) { switch orderType { case Buy: return ob.buyOrders.Length(), nil @@ -108,7 +117,7 @@ func (ob *OrderBook) GetOrderCount(orderType OrderType) (uint64, error) { } // Render returns a string representation of the order book with up to n orders per type -func (ob *OrderBook) Render(n int) string { +func (ob *priorityOrderBook) Render(n int) string { var output string // buy orders diff --git a/examples/gno.land/p/demo/orderbook/orderbook_test.gno b/examples/gno.land/p/demo/orderbook/orderbook_test.gno index bbae1676fad..0e2b0e0b738 100644 --- a/examples/gno.land/p/demo/orderbook/orderbook_test.gno +++ b/examples/gno.land/p/demo/orderbook/orderbook_test.gno @@ -7,7 +7,7 @@ import ( ) func TestOrderBook_AddOrder(t *testing.T) { - ob := NewOrderBook() + ob := NewPriorityOrderBook() order := Order{ ID: "1", @@ -26,7 +26,7 @@ func TestOrderBook_AddOrder(t *testing.T) { } func TestOrderBook_RemoveOrder(t *testing.T) { - ob := NewOrderBook() + ob := NewPriorityOrderBook() order := Order{ ID: "1", @@ -48,7 +48,7 @@ func TestOrderBook_RemoveOrder(t *testing.T) { } func TestOrderBook_PeekOrder(t *testing.T) { - ob := NewOrderBook() + ob := NewPriorityOrderBook() order := Order{ ID: "1", @@ -72,7 +72,7 @@ func TestOrderBook_PeekOrder(t *testing.T) { } func TestOrderBook_GetOrderCount(t *testing.T) { - ob := NewOrderBook() + ob := NewPriorityOrderBook() order := Order{ ID: "1", @@ -96,7 +96,7 @@ func TestOrderBook_GetOrderCount(t *testing.T) { } func TestOrderBook_Render(t *testing.T) { - ob := NewOrderBook() + ob := NewPriorityOrderBook() order1 := Order{ ID: "1", From a44cc6920304b307cc5a1d0048b3528a7534a2c8 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Sun, 14 Jul 2024 21:21:44 +0700 Subject: [PATCH 16/26] use interface for OrderBook --- .../gno.land/p/demo/orderbook/orderbook.gno | 32 +++++++++++++------ .../p/demo/orderbook/orderbook_test.gno | 10 +++--- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/examples/gno.land/p/demo/orderbook/orderbook.gno b/examples/gno.land/p/demo/orderbook/orderbook.gno index 85b3e111a9b..aafdbbc3cbf 100644 --- a/examples/gno.land/p/demo/orderbook/orderbook.gno +++ b/examples/gno.land/p/demo/orderbook/orderbook.gno @@ -29,22 +29,36 @@ type OrderBook interface { Render(n int) string } -// priorityOrderBook represents an order book prioritized by order type (Buy or Sell) -type priorityOrderBook struct { +// sequentialOrderBook represents an order book prioritized by order type (Buy or Sell) +type sequentialOrderBook struct { + buyOrders *dequeue.Dequeue + sellOrders *dequeue.Dequeue +} + +// pricePriorityOrderBook represents an order book prioritized by price +type pricePriorityOrderBook struct { + buyOrders *dequeue.Dequeue + sellOrders *dequeue.Dequeue +} + +// quantityOrderBook represents an order book prioritized by quantity +type quantityPriorityOrderBook struct { buyOrders *dequeue.Dequeue sellOrders *dequeue.Dequeue } // NewOrderBook initializes a new priority order book -func NewPriorityOrderBook() OrderBook { - return &priorityOrderBook{ +func NewSequentialOrderBook() OrderBook { + return &sequentialOrderBook{ buyOrders: dequeue.New(), sellOrders: dequeue.New(), } } +var _ OrderBook = (*sequentialOrderBook)(nil) + // AddOrder adds a new order to the order book -func (ob *priorityOrderBook) AddOrder(order Order) error { +func (ob *sequentialOrderBook) AddOrder(order Order) error { switch order.Type { case Buy: err := ob.buyOrders.PushBack(order) @@ -63,7 +77,7 @@ func (ob *priorityOrderBook) AddOrder(order Order) error { } // RemoveOrder removes the first order from the order book -func (ob *priorityOrderBook) RemoveOrder(orderType OrderType) error { +func (ob *sequentialOrderBook) RemoveOrder(orderType OrderType) error { var err error switch orderType { @@ -85,7 +99,7 @@ func (ob *priorityOrderBook) RemoveOrder(orderType OrderType) error { } // PeekOrder returns the first order in the order book without removing it -func (ob *priorityOrderBook) PeekOrder(orderType OrderType) (Order, error) { +func (ob *sequentialOrderBook) PeekOrder(orderType OrderType) (Order, error) { switch orderType { case Buy: order, err := ob.buyOrders.Front() @@ -105,7 +119,7 @@ func (ob *priorityOrderBook) PeekOrder(orderType OrderType) (Order, error) { } // GetOrderCount returns the number of orders in the order book -func (ob *priorityOrderBook) GetOrderCount(orderType OrderType) (uint64, error) { +func (ob *sequentialOrderBook) GetOrderCount(orderType OrderType) (uint64, error) { switch orderType { case Buy: return ob.buyOrders.Length(), nil @@ -117,7 +131,7 @@ func (ob *priorityOrderBook) GetOrderCount(orderType OrderType) (uint64, error) } // Render returns a string representation of the order book with up to n orders per type -func (ob *priorityOrderBook) Render(n int) string { +func (ob *sequentialOrderBook) Render(n int) string { var output string // buy orders diff --git a/examples/gno.land/p/demo/orderbook/orderbook_test.gno b/examples/gno.land/p/demo/orderbook/orderbook_test.gno index 0e2b0e0b738..49d23a24a06 100644 --- a/examples/gno.land/p/demo/orderbook/orderbook_test.gno +++ b/examples/gno.land/p/demo/orderbook/orderbook_test.gno @@ -7,7 +7,7 @@ import ( ) func TestOrderBook_AddOrder(t *testing.T) { - ob := NewPriorityOrderBook() + ob := NewSequentialOrderBook() order := Order{ ID: "1", @@ -26,7 +26,7 @@ func TestOrderBook_AddOrder(t *testing.T) { } func TestOrderBook_RemoveOrder(t *testing.T) { - ob := NewPriorityOrderBook() + ob := NewSequentialOrderBook() order := Order{ ID: "1", @@ -48,7 +48,7 @@ func TestOrderBook_RemoveOrder(t *testing.T) { } func TestOrderBook_PeekOrder(t *testing.T) { - ob := NewPriorityOrderBook() + ob := NewSequentialOrderBook() order := Order{ ID: "1", @@ -72,7 +72,7 @@ func TestOrderBook_PeekOrder(t *testing.T) { } func TestOrderBook_GetOrderCount(t *testing.T) { - ob := NewPriorityOrderBook() + ob := NewSequentialOrderBook() order := Order{ ID: "1", @@ -96,7 +96,7 @@ func TestOrderBook_GetOrderCount(t *testing.T) { } func TestOrderBook_Render(t *testing.T) { - ob := NewPriorityOrderBook() + ob := NewSequentialOrderBook() order1 := Order{ ID: "1", From 400fb211fb84783251b8180aa5a2c8dd86540080 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Mon, 15 Jul 2024 08:47:05 +0700 Subject: [PATCH 17/26] update comment --- examples/gno.land/p/demo/orderbook/orderbook.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/orderbook/orderbook.gno b/examples/gno.land/p/demo/orderbook/orderbook.gno index aafdbbc3cbf..9612638e0ff 100644 --- a/examples/gno.land/p/demo/orderbook/orderbook.gno +++ b/examples/gno.land/p/demo/orderbook/orderbook.gno @@ -29,7 +29,7 @@ type OrderBook interface { Render(n int) string } -// sequentialOrderBook represents an order book prioritized by order type (Buy or Sell) +// sequentialOrderBook represents an order book prioritized by order insertion sequence type sequentialOrderBook struct { buyOrders *dequeue.Dequeue sellOrders *dequeue.Dequeue From b87114c67ca4cece5cf5d3fd46490e36e87af9f9 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 21 Aug 2024 22:13:53 +0700 Subject: [PATCH 18/26] implement rate_limiter package --- examples/gno.land/p/demo/orderbook/errors.gno | 10 -- examples/gno.land/p/demo/orderbook/gno.mod | 7 - .../gno.land/p/demo/orderbook/orderbook.gno | 163 ------------------ .../p/demo/orderbook/orderbook_test.gno | 144 ---------------- examples/gno.land/p/demo/rate_limiter/gno.mod | 6 + .../p/demo/rate_limiter/rate_limiter.gno | 54 ++++++ .../p/demo/rate_limiter/rate_limiter_test.gno | 51 ++++++ 7 files changed, 111 insertions(+), 324 deletions(-) delete mode 100644 examples/gno.land/p/demo/orderbook/errors.gno delete mode 100644 examples/gno.land/p/demo/orderbook/gno.mod delete mode 100644 examples/gno.land/p/demo/orderbook/orderbook.gno delete mode 100644 examples/gno.land/p/demo/orderbook/orderbook_test.gno create mode 100644 examples/gno.land/p/demo/rate_limiter/gno.mod create mode 100644 examples/gno.land/p/demo/rate_limiter/rate_limiter.gno create mode 100644 examples/gno.land/p/demo/rate_limiter/rate_limiter_test.gno diff --git a/examples/gno.land/p/demo/orderbook/errors.gno b/examples/gno.land/p/demo/orderbook/errors.gno deleted file mode 100644 index 3d8679522b8..00000000000 --- a/examples/gno.land/p/demo/orderbook/errors.gno +++ /dev/null @@ -1,10 +0,0 @@ -package orderbook - -import "errors" - -var ( - ErrFailedToAddOrder = errors.New("failed to add order") - ErrFailedToRemoveOrder = errors.New("failed to remove order") - ErrFailedToPeekOrder = errors.New("failed to peek order") - ErrUnknownOrderType = errors.New("unknown order type") -) diff --git a/examples/gno.land/p/demo/orderbook/gno.mod b/examples/gno.land/p/demo/orderbook/gno.mod deleted file mode 100644 index e335c64a54a..00000000000 --- a/examples/gno.land/p/demo/orderbook/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/r/demo/orderbook - -require ( - gno.land/p/demo/dequeue v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/orderbook/orderbook.gno b/examples/gno.land/p/demo/orderbook/orderbook.gno deleted file mode 100644 index 9612638e0ff..00000000000 --- a/examples/gno.land/p/demo/orderbook/orderbook.gno +++ /dev/null @@ -1,163 +0,0 @@ -package orderbook - -import ( - "gno.land/p/demo/dequeue" - "gno.land/p/demo/ufmt" -) - -type OrderType int - -const ( - Buy OrderType = iota - Sell -) - -// Order represents a single order with ID, type (Buy or Sell), quantity, and price -type Order struct { - ID string // ID of the order - Type OrderType // Type of the order (Buy or Sell) - Quantity int // Quantity of the order - Price uint64 // Price of the order (temporary uint64 due to lack of float support) -} - -// OrderBook defines the interface for an order book -type OrderBook interface { - AddOrder(order Order) error - RemoveOrder(orderType OrderType) error - PeekOrder(orderType OrderType) (Order, error) - GetOrderCount(orderType OrderType) (uint64, error) - Render(n int) string -} - -// sequentialOrderBook represents an order book prioritized by order insertion sequence -type sequentialOrderBook struct { - buyOrders *dequeue.Dequeue - sellOrders *dequeue.Dequeue -} - -// pricePriorityOrderBook represents an order book prioritized by price -type pricePriorityOrderBook struct { - buyOrders *dequeue.Dequeue - sellOrders *dequeue.Dequeue -} - -// quantityOrderBook represents an order book prioritized by quantity -type quantityPriorityOrderBook struct { - buyOrders *dequeue.Dequeue - sellOrders *dequeue.Dequeue -} - -// NewOrderBook initializes a new priority order book -func NewSequentialOrderBook() OrderBook { - return &sequentialOrderBook{ - buyOrders: dequeue.New(), - sellOrders: dequeue.New(), - } -} - -var _ OrderBook = (*sequentialOrderBook)(nil) - -// AddOrder adds a new order to the order book -func (ob *sequentialOrderBook) AddOrder(order Order) error { - switch order.Type { - case Buy: - err := ob.buyOrders.PushBack(order) - if err != nil { - return ErrFailedToAddOrder - } - case Sell: - err := ob.sellOrders.PushBack(order) - if err != nil { - return ErrFailedToAddOrder - } - default: - return ErrUnknownOrderType - } - return nil -} - -// RemoveOrder removes the first order from the order book -func (ob *sequentialOrderBook) RemoveOrder(orderType OrderType) error { - var err error - - switch orderType { - case Buy: - _, err = ob.buyOrders.PopFront() - if err != nil { - return ErrFailedToRemoveOrder - } - case Sell: - _, err = ob.sellOrders.PopFront() - if err != nil { - return ErrFailedToRemoveOrder - } - default: - return ErrUnknownOrderType - } - - return nil -} - -// PeekOrder returns the first order in the order book without removing it -func (ob *sequentialOrderBook) PeekOrder(orderType OrderType) (Order, error) { - switch orderType { - case Buy: - order, err := ob.buyOrders.Front() - if err != nil { - return Order{}, ErrFailedToPeekOrder - } - return order.(Order), nil - case Sell: - order, err := ob.sellOrders.Front() - if err != nil { - return Order{}, ErrFailedToPeekOrder - } - return order.(Order), nil - default: - return Order{}, ErrUnknownOrderType - } -} - -// GetOrderCount returns the number of orders in the order book -func (ob *sequentialOrderBook) GetOrderCount(orderType OrderType) (uint64, error) { - switch orderType { - case Buy: - return ob.buyOrders.Length(), nil - case Sell: - return ob.sellOrders.Length(), nil - default: - return 0, ErrUnknownOrderType - } -} - -// Render returns a string representation of the order book with up to n orders per type -func (ob *sequentialOrderBook) Render(n int) string { - var output string - - // buy orders - output += "Buy Orders:\n" - buyLength := ob.buyOrders.Length() - for i := uint64(0); i < buyLength && i < uint64(n); i++ { - value, err := ob.buyOrders.At(i) - if err != nil { - continue - } - order := value.(Order) - output += ufmt.Sprintf("ID: %s, Quantity: %d, Price: %d\n", order.ID, order.Quantity, order.Price) - } - output += "\n" - - // sell orders - output += "Sell Orders:\n" - sellLength := ob.sellOrders.Length() - for i := uint64(0); i < sellLength && i < uint64(n); i++ { - value, err := ob.sellOrders.At(i) - if err != nil { - continue - } - order := value.(Order) - output += ufmt.Sprintf("ID: %s, Quantity: %d, Price: %d\n", order.ID, order.Quantity, order.Price) - } - - return output -} diff --git a/examples/gno.land/p/demo/orderbook/orderbook_test.gno b/examples/gno.land/p/demo/orderbook/orderbook_test.gno deleted file mode 100644 index 49d23a24a06..00000000000 --- a/examples/gno.land/p/demo/orderbook/orderbook_test.gno +++ /dev/null @@ -1,144 +0,0 @@ -package orderbook - -import ( - "testing" - - "gno.land/p/demo/uassert" -) - -func TestOrderBook_AddOrder(t *testing.T) { - ob := NewSequentialOrderBook() - - order := Order{ - ID: "1", - Type: Buy, - Quantity: 10, - Price: 100, - } - - err := ob.AddOrder(order) - uassert.NoError(t, err, "AddOrder should succeed") - - // Test adding a sell order - order.Type = Sell - err = ob.AddOrder(order) - uassert.NoError(t, err, "AddOrder should succeed for sell order") -} - -func TestOrderBook_RemoveOrder(t *testing.T) { - ob := NewSequentialOrderBook() - - order := Order{ - ID: "1", - Type: Buy, - Quantity: 10, - Price: 100, - } - - err := ob.AddOrder(order) - uassert.NoError(t, err, "AddOrder should succeed") - - // Test removing a buy order - err = ob.RemoveOrder(Buy) - uassert.NoError(t, err, "RemoveOrder should succeed for buy order") - - // Test removing a sell order - err = ob.RemoveOrder(Sell) - uassert.Error(t, err, "RemoveOrder should fail for empty sell orders") -} - -func TestOrderBook_PeekOrder(t *testing.T) { - ob := NewSequentialOrderBook() - - order := Order{ - ID: "1", - Type: Buy, - Quantity: 10, - Price: 100, - } - - err := ob.AddOrder(order) - uassert.NoError(t, err, "AddOrder should succeed") - - // Test peeking a buy order - peekedOrder, err := ob.PeekOrder(Buy) - uassert.NoError(t, err, "PeekOrder should succeed for buy order") - uassert.NotEmpty(t, peekedOrder.ID, "Peeked order should not be nil") - - // Test peeking a sell order - peekedOrder, err = ob.PeekOrder(Sell) - uassert.Error(t, err, "PeekOrder should fail for empty sell orders") - uassert.Empty(t, peekedOrder.ID, "Peeked order should be nil for empty sell orders") -} - -func TestOrderBook_GetOrderCount(t *testing.T) { - ob := NewSequentialOrderBook() - - order := Order{ - ID: "1", - Type: Buy, - Quantity: 10, - Price: 100, - } - - err := ob.AddOrder(order) - uassert.NoError(t, err, "AddOrder should succeed") - - // Test getting order count for buy orders - count, err := ob.GetOrderCount(Buy) - uassert.NoError(t, err, "GetOrderCount should succeed for buy orders") - uassert.Equal(t, uint64(1), count, "Buy order count should be 1") - - // Test getting order count for sell orders - count, err = ob.GetOrderCount(Sell) - uassert.NoError(t, err, "GetOrderCount should succeed for sell orders") - uassert.Equal(t, uint64(0), count, "Sell order count should be 0") -} - -func TestOrderBook_Render(t *testing.T) { - ob := NewSequentialOrderBook() - - order1 := Order{ - ID: "1", - Type: Buy, - Quantity: 10, - Price: 100, - } - order2 := Order{ - ID: "2", - Type: Buy, - Quantity: 15, - Price: 95, - } - order3 := Order{ - ID: "3", - Type: Sell, - Quantity: 5, - Price: 110, - } - order4 := Order{ - ID: "4", - Type: Sell, - Quantity: 8, - Price: 115, - } - - // Add orders to the order book - ob.AddOrder(order1) - ob.AddOrder(order2) - ob.AddOrder(order3) - ob.AddOrder(order4) - - expected := "Buy Orders:\nID: 1, Quantity: 10, Price: 100\n\nSell Orders:\nID: 3, Quantity: 5, Price: 110\n" - result := ob.Render(1) - - uassert.Equal(t, expected, result, "Render() result does not match expected") - - expected = "Buy Orders:\nID: 1, Quantity: 10, Price: 100\nID: 2, Quantity: 15, Price: 95\n\nSell Orders:\nID: 3, Quantity: 5, Price: 110\nID: 4, Quantity: 8, Price: 115\n" - result = ob.Render(2) - - uassert.Equal(t, expected, result, "Render() result does not match expected") - - result = ob.Render(3) - uassert.Equal(t, expected, result, "Render() result does not match expected") -} diff --git a/examples/gno.land/p/demo/rate_limiter/gno.mod b/examples/gno.land/p/demo/rate_limiter/gno.mod new file mode 100644 index 00000000000..f2bad2050cf --- /dev/null +++ b/examples/gno.land/p/demo/rate_limiter/gno.mod @@ -0,0 +1,6 @@ +module ratelimiter + +require ( + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/rate_limiter/rate_limiter.gno b/examples/gno.land/p/demo/rate_limiter/rate_limiter.gno new file mode 100644 index 00000000000..f1d37e00d92 --- /dev/null +++ b/examples/gno.land/p/demo/rate_limiter/rate_limiter.gno @@ -0,0 +1,54 @@ +package ratelimiter + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/dequeue" +) + +// RateLimiter struct to hold the configuration and state of rate limiting +type RateLimiter struct { + MaxActions int // Maximum actions allowed within the window + WindowSize int // The size of the window in blocks + OnchainActions map[std.Address]*dequeue.Dequeue // Maps onchain addresses to action history queue +} + +// New creates a new RateLimiter with the given max actions and window size (in blocks) +func New(maxActions, windowSize int) *RateLimiter { + return &RateLimiter{ + MaxActions: maxActions, + WindowSize: windowSize, + OnchainActions: make(map[std.Address]*dequeue.Dequeue), + } +} + +// AllowAction checks if an onchain address is allowed to perform an action in the current block +func (rl *RateLimiter) AllowAction(address std.Address) bool { + currentBlockHeight := std.GetHeight() + + // Get or create the onchain address's action history queue + actionDeque, exists := rl.OnchainActions[address] + if !exists { + actionDeque = dequeue.New() + rl.OnchainActions[address] = actionDeque + } + + // Remove actions that are outside the window + for !actionDeque.Empty() { + frontBlockHeight, _ := actionDeque.Front() + if frontBlockHeight.(int64) <= currentBlockHeight-int64(rl.WindowSize) { + actionDeque.PopFront() + } else { + break + } + } + + // If the number of valid actions is below the maximum, allow the action + if actionDeque.Length() < uint64(rl.MaxActions) { + actionDeque.PushBack(currentBlockHeight) + return true + } + + return false +} diff --git a/examples/gno.land/p/demo/rate_limiter/rate_limiter_test.gno b/examples/gno.land/p/demo/rate_limiter/rate_limiter_test.gno new file mode 100644 index 00000000000..165437027c0 --- /dev/null +++ b/examples/gno.land/p/demo/rate_limiter/rate_limiter_test.gno @@ -0,0 +1,51 @@ +package ratelimiter + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" +) + +var ( + addr1 = testutils.TestAddress("alice") + addr2 = testutils.TestAddress("bob") +) + +// TestRateLimiter tests the RateLimiter functionality +func TestRateLimiter(t *testing.T) { + // Create a rate limiter with max 5 actions and a window size of 100 blocks + rl := New(5, 100) + + // Set initial block height + std.TestSkipHeights(150) + + // Test cases using urequire + urequire.True(t, rl.AllowAction(addr1), "Expected addr1 to be allowed on first action") + + // Allow additional actions for addr1 + for i := 1; i < 5; i++ { + urequire.True(t, rl.AllowAction(addr1), "Expected addr1 to be allowed within the limit") + } + + // User1 should be blocked after 5 actions + urequire.False(t, rl.AllowAction(addr1), "Expected addr1 to be blocked after exceeding the limit") + + // Fast forward blocks beyond the window size + std.TestSkipHeights(150 + 101) + + // User1 should be allowed again after the window size has passed + urequire.True(t, rl.AllowAction(addr1), "Expected addr1 to be allowed after window size passed") + + // Test actions for another user + urequire.True(t, rl.AllowAction(addr2), "Expected addr2 to be allowed on first action") + + // Allow additional actions for addr2 + for i := 1; i < 5; i++ { + urequire.True(t, rl.AllowAction(addr2), "Expected addr2 to be allowed within the limit") + } + + // User2 should be blocked after 5 actions + urequire.False(t, rl.AllowAction(addr2), "Expected addr2 to be blocked after exceeding the limit") +} From 46b690ef816858afa5f74a8229bc0e0c34fb3325 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 21 Aug 2024 22:16:10 +0700 Subject: [PATCH 19/26] remove imports --- examples/gno.land/p/demo/rate_limiter/rate_limiter.gno | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/gno.land/p/demo/rate_limiter/rate_limiter.gno b/examples/gno.land/p/demo/rate_limiter/rate_limiter.gno index f1d37e00d92..1561b9dc2c6 100644 --- a/examples/gno.land/p/demo/rate_limiter/rate_limiter.gno +++ b/examples/gno.land/p/demo/rate_limiter/rate_limiter.gno @@ -3,7 +3,6 @@ package ratelimiter import ( "std" - "gno.land/p/demo/avl" "gno.land/p/demo/dequeue" ) From 396aef2150414b826b335815262a1f2635dc229f Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 21 Aug 2024 22:19:56 +0700 Subject: [PATCH 20/26] mod tidy --- examples/gno.land/p/demo/rate_limiter/gno.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/gno.land/p/demo/rate_limiter/gno.mod b/examples/gno.land/p/demo/rate_limiter/gno.mod index f2bad2050cf..4381092f6ed 100644 --- a/examples/gno.land/p/demo/rate_limiter/gno.mod +++ b/examples/gno.land/p/demo/rate_limiter/gno.mod @@ -1,6 +1,7 @@ module ratelimiter require ( + gno.land/p/demo/dequeue v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest gno.land/p/demo/urequire v0.0.0-latest ) From 03b1cbf547e87eada500198eab888f71b44b766d Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 21 Aug 2024 22:23:30 +0700 Subject: [PATCH 21/26] fixup --- examples/gno.land/p/demo/rate_limiter/rate_limiter_test.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/rate_limiter/rate_limiter_test.gno b/examples/gno.land/p/demo/rate_limiter/rate_limiter_test.gno index 165437027c0..46dbcb396c2 100644 --- a/examples/gno.land/p/demo/rate_limiter/rate_limiter_test.gno +++ b/examples/gno.land/p/demo/rate_limiter/rate_limiter_test.gno @@ -33,7 +33,7 @@ func TestRateLimiter(t *testing.T) { urequire.False(t, rl.AllowAction(addr1), "Expected addr1 to be blocked after exceeding the limit") // Fast forward blocks beyond the window size - std.TestSkipHeights(150 + 101) + std.TestSkipHeights(100) // User1 should be allowed again after the window size has passed urequire.True(t, rl.AllowAction(addr1), "Expected addr1 to be allowed after window size passed") From 016e7a42097d59a5f5f39f949ba20e98f28a8b85 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 21 Aug 2024 22:42:08 +0700 Subject: [PATCH 22/26] fixup --- examples/gno.land/p/demo/{rate_limiter => ratelimiter}/gno.mod | 0 .../rate_limiter.gno => ratelimiter/ratelimiter.gno} | 0 .../rate_limiter_test.gno => ratelimiter/ratelimiter_test.gno} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename examples/gno.land/p/demo/{rate_limiter => ratelimiter}/gno.mod (100%) rename examples/gno.land/p/demo/{rate_limiter/rate_limiter.gno => ratelimiter/ratelimiter.gno} (100%) rename examples/gno.land/p/demo/{rate_limiter/rate_limiter_test.gno => ratelimiter/ratelimiter_test.gno} (100%) diff --git a/examples/gno.land/p/demo/rate_limiter/gno.mod b/examples/gno.land/p/demo/ratelimiter/gno.mod similarity index 100% rename from examples/gno.land/p/demo/rate_limiter/gno.mod rename to examples/gno.land/p/demo/ratelimiter/gno.mod diff --git a/examples/gno.land/p/demo/rate_limiter/rate_limiter.gno b/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno similarity index 100% rename from examples/gno.land/p/demo/rate_limiter/rate_limiter.gno rename to examples/gno.land/p/demo/ratelimiter/ratelimiter.gno diff --git a/examples/gno.land/p/demo/rate_limiter/rate_limiter_test.gno b/examples/gno.land/p/demo/ratelimiter/ratelimiter_test.gno similarity index 100% rename from examples/gno.land/p/demo/rate_limiter/rate_limiter_test.gno rename to examples/gno.land/p/demo/ratelimiter/ratelimiter_test.gno From bb98f23c19c76452c0b910cb89ce6ed5293cc526 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 21 Aug 2024 22:55:20 +0700 Subject: [PATCH 23/26] update go.mod --- examples/gno.land/p/demo/ratelimiter/gno.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/ratelimiter/gno.mod b/examples/gno.land/p/demo/ratelimiter/gno.mod index 4381092f6ed..968b4feeed5 100644 --- a/examples/gno.land/p/demo/ratelimiter/gno.mod +++ b/examples/gno.land/p/demo/ratelimiter/gno.mod @@ -1,4 +1,4 @@ -module ratelimiter +module gno.land/p/demo/ratelimiter require ( gno.land/p/demo/dequeue v0.0.0-latest From cc406a28444b95d96b585f65294004de46e210f2 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 21 Aug 2024 23:01:55 +0700 Subject: [PATCH 24/26] rename --- examples/gno.land/p/demo/ratelimiter/ratelimiter.gno | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno b/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno index 1561b9dc2c6..88a798f48c0 100644 --- a/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno +++ b/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno @@ -35,8 +35,8 @@ func (rl *RateLimiter) AllowAction(address std.Address) bool { // Remove actions that are outside the window for !actionDeque.Empty() { - frontBlockHeight, _ := actionDeque.Front() - if frontBlockHeight.(int64) <= currentBlockHeight-int64(rl.WindowSize) { + earliestBlockHeight, _ := actionDeque.Front() + if earliestBlockHeight.(int64) <= currentBlockHeight-int64(rl.WindowSize) { actionDeque.PopFront() } else { break From 0416249d40220f185441d12df8e8c72c450395e2 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 21 Aug 2024 23:16:45 +0700 Subject: [PATCH 25/26] new functions --- .../p/demo/ratelimiter/ratelimiter.gno | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno b/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno index 88a798f48c0..442a79efd52 100644 --- a/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno +++ b/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno @@ -8,17 +8,17 @@ import ( // RateLimiter struct to hold the configuration and state of rate limiting type RateLimiter struct { - MaxActions int // Maximum actions allowed within the window - WindowSize int // The size of the window in blocks - OnchainActions map[std.Address]*dequeue.Dequeue // Maps onchain addresses to action history queue + maxActions int // Maximum actions allowed within the window + windowSize int // The size of the window in blocks + onchainActions map[std.Address]*dequeue.Dequeue // Maps onchain addresses to action history queue } // New creates a new RateLimiter with the given max actions and window size (in blocks) func New(maxActions, windowSize int) *RateLimiter { return &RateLimiter{ - MaxActions: maxActions, - WindowSize: windowSize, - OnchainActions: make(map[std.Address]*dequeue.Dequeue), + maxActions: maxActions, + windowSize: windowSize, + onchainActions: make(map[std.Address]*dequeue.Dequeue), } } @@ -27,16 +27,16 @@ func (rl *RateLimiter) AllowAction(address std.Address) bool { currentBlockHeight := std.GetHeight() // Get or create the onchain address's action history queue - actionDeque, exists := rl.OnchainActions[address] + actionDeque, exists := rl.onchainActions[address] if !exists { actionDeque = dequeue.New() - rl.OnchainActions[address] = actionDeque + rl.onchainActions[address] = actionDeque } // Remove actions that are outside the window for !actionDeque.Empty() { earliestBlockHeight, _ := actionDeque.Front() - if earliestBlockHeight.(int64) <= currentBlockHeight-int64(rl.WindowSize) { + if earliestBlockHeight.(int64) <= currentBlockHeight-int64(rl.windowSize) { actionDeque.PopFront() } else { break @@ -44,10 +44,20 @@ func (rl *RateLimiter) AllowAction(address std.Address) bool { } // If the number of valid actions is below the maximum, allow the action - if actionDeque.Length() < uint64(rl.MaxActions) { + if actionDeque.Length() < uint64(rl.maxActions) { actionDeque.PushBack(currentBlockHeight) return true } return false } + +// SetmaxActions updates the maximum number of actions allowed within the time window. +func (rl *RateLimiter) SetmaxActions(maxActions int) { + rl.maxActions = maxActions +} + +// SetwindowSize updates the size of the time window in blocks. +func (rl *RateLimiter) SetwindowSize(windowSize int) { + rl.windowSize = windowSize +} From c6ad96cee4e660f710505e6d6274c4facad0ff2f Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 21 Aug 2024 23:25:28 +0700 Subject: [PATCH 26/26] add unit tests --- .../p/demo/ratelimiter/ratelimiter.gno | 8 ++-- .../p/demo/ratelimiter/ratelimiter_test.gno | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno b/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno index 442a79efd52..5786246bb28 100644 --- a/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno +++ b/examples/gno.land/p/demo/ratelimiter/ratelimiter.gno @@ -52,12 +52,12 @@ func (rl *RateLimiter) AllowAction(address std.Address) bool { return false } -// SetmaxActions updates the maximum number of actions allowed within the time window. -func (rl *RateLimiter) SetmaxActions(maxActions int) { +// SetMaxActions updates the maximum number of actions allowed within the time window. +func (rl *RateLimiter) SetMaxActions(maxActions int) { rl.maxActions = maxActions } -// SetwindowSize updates the size of the time window in blocks. -func (rl *RateLimiter) SetwindowSize(windowSize int) { +// SetWindowSize updates the size of the time window in blocks. +func (rl *RateLimiter) SetWindowSize(windowSize int) { rl.windowSize = windowSize } diff --git a/examples/gno.land/p/demo/ratelimiter/ratelimiter_test.gno b/examples/gno.land/p/demo/ratelimiter/ratelimiter_test.gno index 46dbcb396c2..b0f8dbef537 100644 --- a/examples/gno.land/p/demo/ratelimiter/ratelimiter_test.gno +++ b/examples/gno.land/p/demo/ratelimiter/ratelimiter_test.gno @@ -49,3 +49,45 @@ func TestRateLimiter(t *testing.T) { // User2 should be blocked after 5 actions urequire.False(t, rl.AllowAction(addr2), "Expected addr2 to be blocked after exceeding the limit") } + +// TestSetMaxActions tests the SetMaxActions method of RateLimiter +func TestSetMaxActions(t *testing.T) { + rl := New(2, 100) + + // Set initial block height + std.TestSkipHeights(150) + + // Test allowing actions within the initial limit + urequire.True(t, rl.AllowAction(addr1), "Expected addr1 to be allowed on first action") + urequire.True(t, rl.AllowAction(addr1), "Expected addr1 to be allowed on second action") + + // addr1 should be blocked after exceeding the initial limit + urequire.False(t, rl.AllowAction(addr1), "Expected addr1 to be blocked after exceeding the limit") + + // Update max actions to 3 + rl.SetMaxActions(3) + + // addr1 should be allowed again with updated limit + urequire.True(t, rl.AllowAction(addr1), "Expected addr1 to be allowed after updating max actions") +} + +// TestSetWindowSize tests the SetWindowSize method of RateLimiter +func TestSetWindowSize(t *testing.T) { + rl := New(5, 100) + + // Set initial block height + std.TestSkipHeights(150) + + // Test allowing actions within the initial window size + urequire.True(t, rl.AllowAction(addr1), "Expected addr1 to be allowed on first action") + urequire.True(t, rl.AllowAction(addr1), "Expected addr1 to be allowed within the limit") + + // Update window size to a smaller value + rl.SetWindowSize(2) + + // Fast forward blocks beyond the new window size + std.TestSkipHeights(2) + + // addr1 should be allowed again with updated window size + urequire.True(t, rl.AllowAction(addr1), "Expected addr1 to be allowed after updating window size") +}