Skip to content

Commit 986a9a2

Browse files
committed
WIP
1 parent df514c9 commit 986a9a2

File tree

1 file changed

+88
-1
lines changed

1 file changed

+88
-1
lines changed

capabilities/writetarget/write_target.go

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/hex"
88
"errors"
99
"fmt"
10+
"math/big"
1011
"time"
1112

1213
"go.opentelemetry.io/otel/attribute"
@@ -57,6 +58,8 @@ type TargetStrategy interface {
5758
TransmitReport(ctx context.Context, report []byte, reportContext []byte, signatures [][]byte, request capabilities.CapabilityRequest) (string, error)
5859
// Wrapper around the ChainWriter to get the transaction status
5960
GetTransactionStatus(ctx context.Context, transactionID string) (commontypes.TransactionStatus, error)
61+
// Wrapper around the ChainWriter to get the fee esimate
62+
GetEstimateFee(ctx context.Context, contract string, method string, args any, toAddress string, meta *commontypes.TxMeta, val *big.Int) (commontypes.EstimateFee, error)
6063
}
6164

6265
var (
@@ -74,6 +77,7 @@ const (
7477

7578
type chainService interface {
7679
LatestHead(ctx context.Context) (commontypes.Head, error)
80+
GetTransactionFee(ctx context.Context, transactionID string) (commontypes.ChainFeeComponents, error)
7781
}
7882

7983
type writeTarget struct {
@@ -175,6 +179,53 @@ func success() capabilities.CapabilityResponse {
175179
return capabilities.CapabilityResponse{}
176180
}
177181

182+
// getGasSpendLimit returns the gas spend limit for the given chain ID from the request metadata
183+
func (c *writeTarget) getGasSpendLimit(request capabilities.CapabilityRequest) (string, error) {
184+
spendType := "GAS." + c.chainInfo.ChainID
185+
186+
for _, limit := range request.Metadata.SpendLimits {
187+
if spendType == string(limit.SpendType) {
188+
return limit.Limit, nil
189+
}
190+
}
191+
return "", fmt.Errorf("no gas spend limit found for chain %s", c.chainInfo.ChainID)
192+
}
193+
194+
// checkGasEstimate verifies if the estimated gas fee is within the spend limit and returns the fee
195+
func (c *writeTarget) checkGasEstimate(ctx context.Context, request capabilities.CapabilityRequest) (*big.Int, uint32, error) {
196+
spendLimit, err := c.getGasSpendLimit(request)
197+
if err != nil {
198+
return nil, 0, fmt.Errorf("failed to get gas spend limit, not performing gas estimation: %w", err)
199+
}
200+
201+
// Get gas estimate from ContractWriter
202+
fee, err := c.targetStrategy.GetEstimateFee(ctx, "", "", nil, "", nil, nil)
203+
if err != nil {
204+
return nil, 0, fmt.Errorf("failed to get gas estimate: %w", err)
205+
}
206+
207+
// Convert spend limit from ETH to wei
208+
limitFloat, ok := new(big.Float).SetString(spendLimit)
209+
if !ok {
210+
return nil, 0, fmt.Errorf("invalid gas spend limit format: %s", spendLimit)
211+
}
212+
213+
// Multiply by 10^decimals to convert from ETH to wei
214+
multiplier := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(fee.Decimals)), nil))
215+
limitFloat.Mul(limitFloat, multiplier)
216+
217+
// Convert to big.Int for comparison
218+
limit := new(big.Int)
219+
limitFloat.Int(limit)
220+
221+
// Compare estimate with limit
222+
if fee.Fee.Cmp(limit) > 0 {
223+
return nil, 0, fmt.Errorf("estimated gas fee %s exceeds spend limit %s", fee.Fee.String(), limit.String())
224+
}
225+
226+
return fee.Fee, fee.Decimals, nil
227+
}
228+
178229
func (c *writeTarget) Execute(ctx context.Context, request capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) {
179230
// Take the local timestamp
180231
tsStart := time.Now().UnixMilli()
@@ -189,6 +240,24 @@ func (c *writeTarget) Execute(ctx context.Context, request capabilities.Capabili
189240

190241
c.lggr.Debugw("Execute", "request", request, "capInfo", capInfo)
191242

243+
// Check gas estimate before proceeding
244+
// TODO: discuss if we should release this in a separate PR
245+
fee, _, err := c.checkGasEstimate(ctx, request)
246+
if err != nil {
247+
// Build error message
248+
info := &requestInfo{
249+
tsStart: tsStart,
250+
node: c.nodeAddress,
251+
request: request,
252+
}
253+
errMsg := c.asEmittedError(ctx, &wt.WriteError{
254+
Code: uint32(TransmissionStateFatal),
255+
Summary: "InsufficientFunds",
256+
Cause: err.Error(),
257+
}, "info", info)
258+
return capabilities.CapabilityResponse{}, errMsg
259+
}
260+
192261
// Helper to keep track of the request info
193262
info := requestInfo{
194263
tsStart: tsStart,
@@ -342,7 +411,25 @@ func (c *writeTarget) Execute(ctx context.Context, request capabilities.Capabili
342411
if err != nil {
343412
return capabilities.CapabilityResponse{}, err
344413
}
345-
return success(), nil
414+
415+
// Get the transaction fee
416+
feeComponents, err := c.cs.GetTransactionFee(ctx, txID)
417+
// TODO: implement in EVM + Aptos chain service
418+
if err != nil {
419+
return capabilities.CapabilityResponse{}, fmt.Errorf("failed to get transaction fee: %w", err)
420+
}
421+
422+
return capabilities.CapabilityResponse{
423+
Metadata: capabilities.ResponseMetadata{
424+
Metering: []capabilities.MeteringNodeDetail{
425+
{
426+
Peer2PeerID: "ignored_by_engine",
427+
SpendUnit: "GAS." + c.chainInfo.ChainID,
428+
SpendValue: feeComponents.ExecutionFee.String(),
429+
},
430+
},
431+
},
432+
}, nil
346433
}
347434

348435
func (c *writeTarget) RegisterToWorkflow(ctx context.Context, request capabilities.RegisterToWorkflowRequest) error {

0 commit comments

Comments
 (0)