-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
evm_transfer_controller.go
123 lines (100 loc) · 3.79 KB
/
evm_transfer_controller.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package web
import (
"math/big"
"net/http"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/smartcontractkit/chainlink/v2/core/assets"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas"
"github.com/smartcontractkit/chainlink/v2/core/logger/audit"
"github.com/smartcontractkit/chainlink/v2/core/services/chainlink"
"github.com/smartcontractkit/chainlink/v2/core/store/models"
"github.com/smartcontractkit/chainlink/v2/core/utils"
"github.com/smartcontractkit/chainlink/v2/core/web/presenters"
"github.com/gin-gonic/gin"
)
// EVMTransfersController can send LINK tokens to another address
type EVMTransfersController struct {
App chainlink.Application
}
// Create sends ETH from the Chainlink's account to a specified address.
//
// Example: "<application>/withdrawals"
func (tc *EVMTransfersController) Create(c *gin.Context) {
var tr models.SendEtherRequest
if err := c.ShouldBindJSON(&tr); err != nil {
jsonAPIError(c, http.StatusBadRequest, err)
return
}
chain, err := getChain(tc.App.GetChains().EVM, tr.EVMChainID.String())
switch err {
case ErrInvalidChainID, ErrMultipleChains, ErrMissingChainID:
jsonAPIError(c, http.StatusUnprocessableEntity, err)
return
case nil:
break
default:
jsonAPIError(c, http.StatusInternalServerError, err)
return
}
if tr.FromAddress == utils.ZeroAddress {
jsonAPIError(c, http.StatusUnprocessableEntity, errors.Errorf("withdrawal source address is missing: %v", tr.FromAddress))
return
}
if !tr.AllowHigherAmounts {
err = ValidateEthBalanceForTransfer(c, chain, tr.FromAddress, tr.Amount)
if err != nil {
jsonAPIError(c, http.StatusUnprocessableEntity, errors.Errorf("transaction failed: %v", err))
return
}
}
etx, err := chain.TxManager().SendNativeToken(chain.ID(), tr.FromAddress, tr.DestinationAddress, *tr.Amount.ToInt(), chain.Config().EvmGasLimitTransfer())
if err != nil {
jsonAPIError(c, http.StatusBadRequest, errors.Errorf("transaction failed: %v", err))
return
}
tc.App.GetAuditLogger().Audit(audit.EthTransactionCreated, map[string]interface{}{
"ethTX": etx,
})
jsonAPIResponse(c, presenters.NewEthTxResource(etx), "eth_tx")
}
// ValidateEthBalanceForTransfer validates that the current balance can cover the transaction amount
func ValidateEthBalanceForTransfer(c *gin.Context, chain evm.Chain, fromAddr common.Address, amount assets.Eth) error {
var err error
var balance *big.Int
balanceMonitor := chain.BalanceMonitor()
if balanceMonitor != nil {
balance = balanceMonitor.GetEthBalance(fromAddr).ToInt()
} else {
balance, err = chain.Client().BalanceAt(c, fromAddr, nil)
if err != nil {
return err
}
}
zero := big.NewInt(0)
if balance == nil || balance.Cmp(zero) == 0 {
return errors.Errorf("balance is too low for this transaction to be executed: %v", balance)
}
var fees gas.EvmFee
gasLimit := chain.Config().EvmGasLimitTransfer()
estimator := chain.GasEstimator()
fees, gasLimit, err = estimator.GetFee(c, nil, gasLimit, chain.Config().KeySpecificMaxGasPriceWei(fromAddr))
if err != nil {
return errors.Wrap(err, "failed to estimate gas")
}
// TODO: support EIP-1559 transactions
if fees.Legacy == nil {
return errors.New("estimator did not return legacy tx fee estimates")
}
gasPrice := fees.Legacy
// Creating a `Big` struct to avoid having a mutation on `tr.Amount` and hence affecting the value stored in the DB
amountAsBig := utils.NewBig(amount.ToInt())
fee := new(big.Int).Mul(gasPrice.ToInt(), big.NewInt(int64(gasLimit)))
amountWithFees := new(big.Int).Add(amountAsBig.ToInt(), fee)
if balance.Cmp(amountWithFees) < 0 {
// ETH balance is less than the sent amount + fees
return errors.Errorf("balance is too low for this transaction to be executed: %v", balance)
}
return nil
}