Skip to content

Commit 180db6d

Browse files
authored
Merge pull request #133 from String-xyz/fix/sean/quotePayloadFloatPrecision
Fix/sean/quote payload float precision
2 parents 92df2ae + 0f5bdc3 commit 180db6d

File tree

5 files changed

+120
-32
lines changed

5 files changed

+120
-32
lines changed

api/handler/transact.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func NewTransaction(route *echo.Echo, service service.Transaction) Transaction {
2424
}
2525

2626
func (t transaction) Transact(c echo.Context) error {
27-
var body model.ExecutionRequest
27+
var body model.PrecisionSafeExecutionRequest
2828
err := c.Bind(&body)
2929
if err != nil {
3030
LogStringError(c, err, "transact: execute bind")

pkg/internal/common/precision.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package common
2+
3+
import (
4+
"strconv"
5+
6+
"github.com/String-xyz/string-api/pkg/model"
7+
)
8+
9+
func QuoteToPrecise(imprecise model.Quote) model.PrecisionSafeQuote {
10+
res := model.PrecisionSafeQuote{
11+
Timestamp: imprecise.Timestamp,
12+
BaseUSD: strconv.FormatFloat(imprecise.BaseUSD, 'f', 2, 64),
13+
GasUSD: strconv.FormatFloat(imprecise.GasUSD, 'f', 2, 64),
14+
TokenUSD: strconv.FormatFloat(imprecise.TokenUSD, 'f', 2, 64),
15+
ServiceUSD: strconv.FormatFloat(imprecise.ServiceUSD, 'f', 2, 64),
16+
TotalUSD: strconv.FormatFloat(imprecise.TotalUSD, 'f', 2, 64),
17+
}
18+
return res
19+
}
20+
21+
func QuoteToImprecise(precise model.PrecisionSafeQuote) model.Quote {
22+
res := model.Quote{
23+
Timestamp: precise.Timestamp,
24+
}
25+
res.BaseUSD, _ = strconv.ParseFloat(precise.BaseUSD, 64)
26+
res.GasUSD, _ = strconv.ParseFloat(precise.GasUSD, 64)
27+
res.TokenUSD, _ = strconv.ParseFloat(precise.TokenUSD, 64)
28+
res.ServiceUSD, _ = strconv.ParseFloat(precise.ServiceUSD, 64)
29+
res.TotalUSD, _ = strconv.ParseFloat(precise.TotalUSD, 64)
30+
return res
31+
}
32+
33+
func ExecutionRequestToPrecise(imprecise model.ExecutionRequest) model.PrecisionSafeExecutionRequest {
34+
res := model.PrecisionSafeExecutionRequest{
35+
TransactionRequest: imprecise.TransactionRequest,
36+
PrecisionSafeQuote: QuoteToPrecise(imprecise.Quote),
37+
Signature: imprecise.Signature,
38+
CardToken: imprecise.CardToken,
39+
}
40+
return res
41+
}
42+
43+
func ExecutionRequestToImprecise(precise model.PrecisionSafeExecutionRequest) model.ExecutionRequest {
44+
res := model.ExecutionRequest{
45+
TransactionRequest: precise.TransactionRequest,
46+
Quote: QuoteToImprecise(precise.PrecisionSafeQuote),
47+
Signature: precise.Signature,
48+
CardToken: precise.CardToken,
49+
}
50+
return res
51+
}

pkg/model/transaction.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,26 @@ type ExecutionRequest struct {
2424
CardToken string `json:"cardToken"`
2525
}
2626

27+
type PrecisionSafeQuote struct {
28+
Timestamp int64 `json:"timestamp"`
29+
BaseUSD string `json:"baseUSD"`
30+
GasUSD string `json:"gasUSD"`
31+
TokenUSD string `json:"tokenUSD"`
32+
ServiceUSD string `json:"serviceUSD"`
33+
TotalUSD string `json:"totalUSD"`
34+
}
35+
36+
type PrecisionSafeExecutionRequest struct {
37+
TransactionRequest
38+
PrecisionSafeQuote
39+
Signature string `json:"signature"`
40+
CardToken string `json:"cardToken"`
41+
}
42+
2743
// User will pass this in for a quote and receive Execution Parameters
2844
type TransactionRequest struct {
2945
UserAddress string `json:"userAddress"` // Used to keep track of user ie "0x44A4b9E2A69d86BA382a511f845CbF2E31286770"
30-
ChainId int `json:"chainId"` // Chain ID to execute on e.g. 80000
46+
ChainId uint64 `json:"chainId"` // Chain ID to execute on e.g. 80000
3147
CxAddr string `json:"contractAddress"` // Address of contract ie "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
3248
CxFunc string `json:"contractFunction"` // Function declaration ie "mintTo(address) payable"
3349
CxReturn string `json:"contractReturn"` // Function return type ie "uint256"

pkg/service/cost.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package service
22

33
import (
4+
"math"
45
"math/big"
56
"os"
67
"time"
@@ -110,8 +111,18 @@ func (c cost) EstimateTransaction(p EstimationParams, chain Chain) (model.Quote,
110111
gasInUSD = 0.01
111112
}
112113

114+
// Round up to nearest cent
115+
transactionCost = centCeiling(transactionCost)
116+
gasInUSD = centCeiling(gasInUSD)
117+
tokenCost = centCeiling(tokenCost)
118+
serviceFee = centCeiling(serviceFee)
119+
120+
// sum total
113121
totalUSD := transactionCost + gasInUSD + tokenCost + serviceFee
114122

123+
// Round that up as well to account for any floating imprecision
124+
totalUSD = centCeiling(totalUSD)
125+
115126
// Fill out CostEstimate and return
116127
return model.Quote{
117128
Timestamp: timestamp,
@@ -123,6 +134,10 @@ func (c cost) EstimateTransaction(p EstimationParams, chain Chain) (model.Quote,
123134
}, nil
124135
}
125136

137+
func centCeiling(value float64) float64 {
138+
return math.Ceil(value*100) / 100
139+
}
140+
126141
func (c cost) getExternalAPICallInterval(rateLimitPerMinute float64, uniqueEntries uint32) int64 {
127142
return int64(float64(60*rateLimitPerMinute) / rateLimitPerMinute)
128143
}

pkg/service/transaction.go

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import (
2020
)
2121

2222
type Transaction interface {
23-
Quote(d model.TransactionRequest) (model.ExecutionRequest, error)
24-
Execute(e model.ExecutionRequest, userId string, deviceId string, ip string) (model.TransactionReceipt, error)
23+
Quote(d model.TransactionRequest) (model.PrecisionSafeExecutionRequest, error)
24+
Execute(e model.PrecisionSafeExecutionRequest, userId string, deviceId string, ip string) (model.TransactionReceipt, error)
2525
}
2626

2727
type TransactionRepos struct {
@@ -55,27 +55,28 @@ func NewTransaction(repos repository.Repositories, redis store.RedisStore, unit2
5555
}
5656

5757
type transactionProcessingData struct {
58-
userId *string
59-
user *model.User
60-
deviceId *string
61-
ip *string
62-
executor *Executor
63-
processingFeeAsset *model.Asset
64-
transactionModel *model.Transaction
65-
chain *Chain
66-
executionRequest *model.ExecutionRequest
67-
cardAuthorization *AuthorizedCharge
68-
cardCapture *payments.CapturesResponse
69-
preBalance *float64
70-
recipientWalletId *string
71-
txId *string
72-
cumulativeValue *big.Int
73-
trueGas *uint64
58+
userId *string
59+
user *model.User
60+
deviceId *string
61+
ip *string
62+
executor *Executor
63+
processingFeeAsset *model.Asset
64+
transactionModel *model.Transaction
65+
chain *Chain
66+
executionRequest *model.ExecutionRequest
67+
precisionSafeExecutionRequest *model.PrecisionSafeExecutionRequest
68+
cardAuthorization *AuthorizedCharge
69+
cardCapture *payments.CapturesResponse
70+
preBalance *float64
71+
recipientWalletId *string
72+
txId *string
73+
cumulativeValue *big.Int
74+
trueGas *uint64
7475
}
7576

76-
func (t transaction) Quote(d model.TransactionRequest) (model.ExecutionRequest, error) {
77+
func (t transaction) Quote(d model.TransactionRequest) (model.PrecisionSafeExecutionRequest, error) {
7778
// TODO: use prefab service to parse d and fill out known params
78-
res := model.ExecutionRequest{TransactionRequest: d}
79+
res := model.PrecisionSafeExecutionRequest{TransactionRequest: d}
7980
// chain, err := model.ChainInfo(uint64(d.ChainId))
8081
chain, err := ChainInfo(uint64(d.ChainId), t.repos.Network, t.repos.Asset)
8182
if err != nil {
@@ -91,7 +92,7 @@ func (t transaction) Quote(d model.TransactionRequest) (model.ExecutionRequest,
9192
if err != nil {
9293
return res, common.StringError(err)
9394
}
94-
res.Quote = estimateUSD
95+
res.PrecisionSafeQuote = common.QuoteToPrecise(estimateUSD)
9596
executor.Close()
9697

9798
// Sign entire payload
@@ -108,9 +109,9 @@ func (t transaction) Quote(d model.TransactionRequest) (model.ExecutionRequest,
108109
return res, nil
109110
}
110111

111-
func (t transaction) Execute(e model.ExecutionRequest, userId string, deviceId string, ip string) (res model.TransactionReceipt, err error) {
112+
func (t transaction) Execute(e model.PrecisionSafeExecutionRequest, userId string, deviceId string, ip string) (res model.TransactionReceipt, err error) {
112113
t.getStringInstrumentsAndUserId()
113-
p := transactionProcessingData{executionRequest: &e, userId: &userId, deviceId: &deviceId, ip: &ip}
114+
p := transactionProcessingData{precisionSafeExecutionRequest: &e, executionRequest: &model.ExecutionRequest{}, userId: &userId, deviceId: &deviceId, ip: &ip}
114115

115116
// Pre-flight transaction setup
116117
p, err = t.transactionSetup(p)
@@ -153,7 +154,7 @@ func (t transaction) transactionSetup(p transactionProcessingData) (transactionP
153154
p.user = &user
154155

155156
// Pull chain info needed for execution from repository
156-
chain, err := ChainInfo(uint64(p.executionRequest.ChainId), t.repos.Network, t.repos.Asset)
157+
chain, err := ChainInfo(p.precisionSafeExecutionRequest.ChainId, t.repos.Network, t.repos.Asset)
157158
if err != nil {
158159
return p, common.StringError(err)
159160
}
@@ -167,7 +168,7 @@ func (t transaction) transactionSetup(p transactionProcessingData) (transactionP
167168
p.transactionModel = &transactionModel
168169

169170
updateDB := &model.TransactionUpdates{}
170-
processingFeeAsset, err := t.populateInitialTxModelData(*p.executionRequest, updateDB)
171+
processingFeeAsset, err := t.populateInitialTxModelData(*p.precisionSafeExecutionRequest, updateDB)
171172
p.processingFeeAsset = &processingFeeAsset
172173
if err != nil {
173174
return p, common.StringError(err)
@@ -196,7 +197,7 @@ func (t transaction) transactionSetup(p transactionProcessingData) (transactionP
196197

197198
func (t transaction) safetyCheck(p transactionProcessingData) (transactionProcessingData, error) {
198199
// Test the Tx and update model status
199-
estimateUSD, estimateETH, err := t.testTransaction(*p.executor, p.executionRequest.TransactionRequest, *p.chain, false)
200+
estimateUSD, estimateETH, err := t.testTransaction(*p.executor, p.precisionSafeExecutionRequest.TransactionRequest, *p.chain, false)
200201
if err != nil {
201202
return p, common.StringError(err)
202203
}
@@ -206,14 +207,15 @@ func (t transaction) safetyCheck(p transactionProcessingData) (transactionProces
206207
}
207208

208209
// Verify the Quote and update model status
209-
_, err = verifyQuote(*p.executionRequest, estimateUSD)
210+
_, err = verifyQuote(*p.precisionSafeExecutionRequest, estimateUSD)
210211
if err != nil {
211212
return p, common.StringError(err)
212213
}
213214
err = t.updateTransactionStatus("Quote Verified", p.transactionModel.Id)
214215
if err != nil {
215216
return p, common.StringError(err)
216217
}
218+
*p.executionRequest = common.ExecutionRequestToImprecise(*p.precisionSafeExecutionRequest)
217219

218220
// Get current balance of primary token
219221
preBalance, err := (*p.executor).GetBalance()
@@ -442,7 +444,7 @@ func (t transaction) postProcess(p transactionProcessingData) {
442444
}
443445
}
444446

445-
func (t transaction) populateInitialTxModelData(e model.ExecutionRequest, m *model.TransactionUpdates) (model.Asset, error) {
447+
func (t transaction) populateInitialTxModelData(e model.PrecisionSafeExecutionRequest, m *model.TransactionUpdates) (model.Asset, error) {
446448
txType := "fiat-to-crypto"
447449
m.Type = &txType
448450
// TODO populate transactionModel.Tags with key-val pairs for Unit21
@@ -510,7 +512,7 @@ func (t transaction) testTransaction(executor Executor, request model.Transactio
510512
return res, eth, nil
511513
}
512514

513-
func verifyQuote(e model.ExecutionRequest, newEstimate model.Quote) (bool, error) {
515+
func verifyQuote(e model.PrecisionSafeExecutionRequest, newEstimate model.Quote) (bool, error) {
514516
// Null out values which have changed since payload was signed
515517
dataToValidate := e
516518
dataToValidate.Signature = ""
@@ -529,7 +531,11 @@ func verifyQuote(e model.ExecutionRequest, newEstimate model.Quote) (bool, error
529531
if newEstimate.Timestamp-e.Timestamp > 20 {
530532
return false, common.StringError(errors.New("verifyQuote: quote expired"))
531533
}
532-
if newEstimate.TotalUSD > e.TotalUSD {
534+
quotedTotal, err := strconv.ParseFloat(e.TotalUSD, 64)
535+
if err != nil {
536+
return false, common.StringError(err)
537+
}
538+
if newEstimate.TotalUSD > quotedTotal {
533539
return false, common.StringError(errors.New("verifyQuote: price too volatile"))
534540
}
535541
return true, nil

0 commit comments

Comments
 (0)