Skip to content

Commit

Permalink
Merge pull request #51 from ElrondNetwork/construction-refactor-05
Browse files Browse the repository at this point in the history
Refactor construction API
  • Loading branch information
andreibancioiu authored Oct 11, 2022
2 parents 6b92c67 + 94721c5 commit d34f31e
Show file tree
Hide file tree
Showing 14 changed files with 948 additions and 644 deletions.
60 changes: 60 additions & 0 deletions server/services/common.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,63 @@
package services

import (
"encoding/json"
"math/big"
"strings"
)

type objectsMap map[string]interface{}

func toObjectsMap(value any) (objectsMap, error) {
data, err := json.Marshal(value)
if err != nil {
return nil, err
}

var result objectsMap
err = json.Unmarshal(data, &result)
if err != nil {
return nil, err
}

return result, nil
}

func fromObjectsMap(obj objectsMap, value any) error {
data, err := json.Marshal(obj)
if err != nil {
return err
}

err = json.Unmarshal(data, value)
if err != nil {
return err
}

return nil
}

func isZeroAmount(amount string) bool {
if amount == "" {
return true
}

value, ok := big.NewInt(0).SetString(amount, 10)
if ok {
return value.Sign() == 0
}

return false
}

func getMagnitudeOfAmount(amount string) string {
return strings.Trim(amount, "-")
}

func multiplyUint64(a uint64, b uint64) *big.Int {
return big.NewInt(0).Mul(big.NewInt(0).SetUint64(a), big.NewInt(0).SetUint64(b))
}

func addBigInt(a *big.Int, b *big.Int) *big.Int {
return big.NewInt(0).Add(a, b)
}
57 changes: 57 additions & 0 deletions server/services/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package services

import (
"math"
"math/big"
"testing"

"github.com/stretchr/testify/require"
)

type dummy struct {
A string `json:"a"`
B string `json:"b"`
C uint64 `json:"c"`
}

func Test_ToObjectsMapAndFromObjectsMap(t *testing.T) {
t.Parallel()

dummyOriginal := &dummy{
A: "a",
B: "b",
C: 42,
}

dummyMap, err := toObjectsMap(dummyOriginal)
require.Nil(t, err)

dummyConverted := &dummy{}
err = fromObjectsMap(dummyMap, dummyConverted)
require.Nil(t, err)

require.Equal(t, dummyOriginal, dummyConverted)
}

func Test_IsZeroAmount(t *testing.T) {
require.True(t, isZeroAmount(""))
require.True(t, isZeroAmount("0"))
require.True(t, isZeroAmount("-0"))
require.True(t, isZeroAmount("00"))
require.False(t, isZeroAmount("1"))
require.False(t, isZeroAmount("-1"))
}

func Test_GetMagnitudeOfAmount(t *testing.T) {
require.Equal(t, "100", getMagnitudeOfAmount("100"))
require.Equal(t, "100", getMagnitudeOfAmount("-100"))
}

func Test_MultiplyUint64(t *testing.T) {
require.Equal(t, "340282366920938463426481119284349108225", multiplyUint64(math.MaxUint64, math.MaxUint64).String())
require.Equal(t, "1", multiplyUint64(1, 1).String())
}

func Test_AddBigInt(t *testing.T) {
require.Equal(t, "12", addBigInt(big.NewInt(7), big.NewInt(5)).String())
}
90 changes: 90 additions & 0 deletions server/services/constructionMetadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package services

import (
"encoding/json"
"errors"
"fmt"

"github.com/ElrondNetwork/elrond-proxy-go/data"
)

type constructionMetadata struct {
Sender string `json:"sender"`
Receiver string `json:"receiver"`
Nonce uint64 `json:"nonce"`
Amount string `json:"amount"`
CurrencySymbol string `json:"currencySymbol"`
GasLimit uint64 `json:"gasLimit"`
GasPrice uint64 `json:"gasPrice"`
Data []byte `json:"data"`
ChainID string `json:"chainID"`
Version int `json:"version"`
}

func newConstructionMetadata(obj objectsMap) (*constructionMetadata, error) {
result := &constructionMetadata{}
err := fromObjectsMap(obj, result)
if err != nil {
return nil, err
}

return result, nil
}

func (metadata *constructionMetadata) toTransactionJson() ([]byte, error) {
tx, err := metadata.toTransaction()
if err != nil {
return nil, err
}

txJson, err := json.Marshal(tx)
if err != nil {
return nil, err
}

return txJson, nil
}

func (metadata *constructionMetadata) toTransaction() (*data.Transaction, error) {
err := metadata.validate()
if err != nil {
return nil, err
}

tx := &data.Transaction{
Sender: metadata.Sender,
Receiver: metadata.Receiver,
Nonce: metadata.Nonce,
Value: metadata.Amount,
GasLimit: metadata.GasLimit,
GasPrice: metadata.GasPrice,
Data: metadata.Data,
ChainID: metadata.ChainID,
Version: uint32(metadata.Version),
}

return tx, nil
}

func (metadata *constructionMetadata) validate() error {
if len(metadata.Sender) == 0 {
return errors.New("missing metadata: 'sender'")
}
if len(metadata.Receiver) == 0 {
return errors.New("missing metadata: 'receiver'")
}
if metadata.GasLimit == 0 {
return errors.New("missing metadata: 'gasLimit'")
}
if metadata.GasPrice == 0 {
return errors.New("missing metadata: 'gasPrice'")
}
if metadata.Version != 1 {
return fmt.Errorf("bad metadata: unexpected 'version' %v", metadata.Version)
}
if len(metadata.ChainID) == 0 {
return errors.New("missing metadata: 'chainID'")
}

return nil
}
82 changes: 82 additions & 0 deletions server/services/constructionMetadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package services

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestConstructionMetadata_ToTransactionJson(t *testing.T) {
t.Parallel()

options := &constructionMetadata{
Sender: "alice",
Receiver: "bob",
Nonce: 42,
Amount: "1234",
CurrencySymbol: "XeGLD",
GasLimit: 80000,
GasPrice: 1000000000,
Data: []byte("hello"),
ChainID: "T",
Version: 1,
}

expectedJson := `{"nonce":42,"value":"1234","receiver":"bob","sender":"alice","gasPrice":1000000000,"gasLimit":80000,"data":"aGVsbG8=","chainID":"T","version":1}`
actualJson, err := options.toTransactionJson()
require.Nil(t, err)
require.Equal(t, expectedJson, string(actualJson))
}

func TestConstructionMetadata_Validate(t *testing.T) {
t.Parallel()

require.ErrorContains(t, (&constructionMetadata{}).validate(), "missing metadata: 'sender'")

require.ErrorContains(t, (&constructionMetadata{
Sender: "alice",
}).validate(), "missing metadata: 'receiver'")

require.ErrorContains(t, (&constructionMetadata{
Sender: "alice",
Receiver: "bob",
}).validate(), "missing metadata: 'gasLimit'")

require.ErrorContains(t, (&constructionMetadata{
Sender: "alice",
Receiver: "bob",
GasLimit: 50000,
}).validate(), "missing metadata: 'gasPrice'")

require.ErrorContains(t, (&constructionMetadata{
Sender: "alice",
Receiver: "bob",
GasLimit: 50000,
GasPrice: 1000000000,
}).validate(), "bad metadata: unexpected 'version' 0")

require.ErrorContains(t, (&constructionMetadata{
Sender: "alice",
Receiver: "bob",
GasLimit: 50000,
GasPrice: 1000000000,
Version: 42,
}).validate(), "bad metadata: unexpected 'version' 42")

require.ErrorContains(t, (&constructionMetadata{
Sender: "alice",
Receiver: "bob",
GasLimit: 50000,
GasPrice: 1000000000,
Version: 1,
}).validate(), "missing metadata: 'chainID'")

require.Nil(t, (&constructionMetadata{
Sender: "alice",
Receiver: "bob",
GasLimit: 50000,
GasPrice: 1000000000,
Version: 1,
ChainID: "T",
}).validate())
}
61 changes: 61 additions & 0 deletions server/services/constructionOptions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package services

import (
"errors"
)

type constructionOptions struct {
Sender string `json:"sender"`
Receiver string `json:"receiver"`
Amount string `json:"amount"`
CurrencySymbol string `json:"currencySymbol"`
GasLimit uint64 `json:"gasLimit"`
GasPrice uint64 `json:"gasPrice"`
Data []byte `json:"data"`
}

func newConstructionOptions(obj objectsMap) (*constructionOptions, error) {
result := &constructionOptions{}
err := fromObjectsMap(obj, result)
if err != nil {
return nil, err
}

return result, nil
}

func (options *constructionOptions) coalesceGasLimit(estimatedGasLimit uint64) uint64 {
if options.GasLimit == 0 {
return estimatedGasLimit
}

return options.GasLimit
}

func (options *constructionOptions) coalesceGasPrice(minGasPrice uint64) uint64 {
if options.GasPrice == 0 {
return minGasPrice
}

return options.GasPrice
}

func (options *constructionOptions) validate(nativeCurrencySymbol string) error {
if len(options.Sender) == 0 {
return errors.New("missing option: 'sender'")
}
if len(options.Receiver) == 0 {
return errors.New("missing option: 'receiver'")
}
if isZeroAmount(options.Amount) {
return errors.New("missing option: 'amount'")
}
if len(options.CurrencySymbol) == 0 {
return errors.New("missing option: 'currencySymbol'")
}
if len(options.Data) > 0 && options.CurrencySymbol != nativeCurrencySymbol {
return errors.New("for custom currencies, option 'data' must be empty")
}

return nil
}
Loading

0 comments on commit d34f31e

Please sign in to comment.