Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: use ERC20Permit built ABI instaed of manual tx creation #7

Merged
merged 1 commit into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions internal/service/api/handlers/create_airdrop.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ package handlers

import (
stdErrors "errors"
"math/big"
"net/http"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/rarimo/evm-airdrop-svc/internal/data"
"github.com/rarimo/evm-airdrop-svc/internal/service/api"
"github.com/rarimo/evm-airdrop-svc/internal/service/api/models"
Expand Down Expand Up @@ -67,19 +64,10 @@ func CreateAirdrop(w http.ResponseWriter, r *http.Request) {
return
}

tokenDecimals, err := api.ERC20Permit(r).Decimals(&bind.CallOpts{})
if err != nil {
api.Log(r).WithError(err).WithFields(logan.F{
"address": api.AirdropConfig(r).TokenAddress,
}).Error("failed to get token decimals")
ape.RenderErr(w, problems.InternalError())
return
}

airdrop, err = api.AirdropsQ(r).Insert(data.Airdrop{
Nullifier: nullifier,
Address: req.Data.Attributes.Address,
Amount: new(big.Int).Mul(api.AirdropConfig(r).Amount, math.BigPow(10, int64(tokenDecimals))).String(),
Amount: api.AirdropConfig(r).Amount.String(),
Status: data.TxStatusPending,
})
if err != nil {
Expand Down
3 changes: 0 additions & 3 deletions internal/service/api/requests/transfer_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package requests
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"regexp"
"time"
Expand Down Expand Up @@ -87,8 +86,6 @@ func VerifyPermitSignature(r *http.Request, attrs resources.TransferErc20TokenAt
recoveredAddr := crypto.PubkeyToAddress(*pubKey)

if !bytes.Equal(recoveredAddr.Bytes(), attrs.Sender.Bytes()) {
fmt.Println(recoveredAddr.Hex())
fmt.Println(attrs.Sender.Hex())
return errors.New("recovered pubkey is invalid")
}

Expand Down
79 changes: 30 additions & 49 deletions internal/service/broadcaster/broadcaster.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,38 @@ import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/rarimo/evm-airdrop-svc/contracts"
"github.com/rarimo/evm-airdrop-svc/internal/config"
"github.com/rarimo/evm-airdrop-svc/internal/data"
"gitlab.com/distributed_lab/logan/v3"
"gitlab.com/distributed_lab/logan/v3/errors"
"gitlab.com/distributed_lab/running"
)

const (
byteSize = 32
transferFnSignature = "transfer(address,uint256)"
)

type Runner struct {
log *logan.Entry
q *data.AirdropsQ
config.Broadcaster
config.AirdropConfig
erc20 *contracts.ERC20Permit
}

func Run(ctx context.Context, cfg *config.Config) {
log := cfg.Log().WithField("service", "builtin-broadcaster")
log.Info("Starting service")

erc20Permit, err := contracts.NewERC20Permit(cfg.AirdropConfig().TokenAddress, cfg.Broadcaster().RPC)
if err != nil {
panic(errors.Wrap(err, "failed to init erc20 permit transfer contract"))
}

r := &Runner{
log: log,
q: data.NewAirdropsQ(cfg.DB().Clone()),
Broadcaster: cfg.Broadcaster(),
AirdropConfig: cfg.AirdropConfig(),
erc20: erc20Permit,
}

running.WithBackOff(ctx, r.log, "builtin-broadcaster", r.run, 5*time.Second, 5*time.Second, 5*time.Second)
Expand Down Expand Up @@ -93,27 +94,14 @@ func (r *Runner) handlePending(ctx context.Context, airdrop data.Airdrop) (err e
}

func (r *Runner) genTx(ctx context.Context, airdrop data.Airdrop) (*types.Transaction, error) {
receiver := common.HexToAddress(airdrop.Address)
txData := r.buildTransferTx(airdrop)

gasPrice, gasLimit, err := r.getGasCosts(ctx, receiver, txData)
if err != nil {
return nil, fmt.Errorf("failed to get gas costs: %w", err)
bigAmount, ok := new(big.Int).SetString(airdrop.Amount, 10)
if !ok {
return nil, fmt.Errorf("failed to parse amount: %s", airdrop.Amount)
}

tx, err := types.SignNewTx(
r.PrivateKey,
types.NewCancunSigner(r.ChainID),
&types.LegacyTx{
Nonce: r.Nonce(),
Gas: gasLimit,
GasPrice: gasPrice,
To: &receiver,
Data: txData,
},
)
tx, err := r.getTransferTx(ctx, common.HexToAddress(airdrop.Address), bigAmount)
if err != nil {
return nil, fmt.Errorf("failed to sign new tx: %w", err)
return nil, fmt.Errorf("failed to get transfer params: %w", err)
}

return tx, nil
Expand Down Expand Up @@ -147,10 +135,12 @@ func (r *Runner) waitForTransactionMined(ctx context.Context, transaction *types
if err != nil {
log.WithError(err).WithField("transaction", transaction).Error("failed to get tx error")
r.updateAirdropStatus(ctx, airdrop.ID, transaction.Hash().String(), data.TxStatusFailed, err)
return
}

log.WithError(err).WithField("transaction", transaction).Error("transaction was mined with failed status")
r.updateAirdropStatus(ctx, airdrop.ID, transaction.Hash().String(), data.TxStatusFailed, txErr)
return
}

r.updateAirdropStatus(ctx, airdrop.ID, transaction.Hash().String(), data.TxStatusCompleted, nil)
Expand Down Expand Up @@ -204,36 +194,27 @@ func (r *Runner) updateAirdropStatus(ctx context.Context, id, txHash, status str
}, 2*time.Second, 10*time.Second)
}

func (r *Runner) buildTransferTx(airdrop data.Airdrop) []byte {
methodID := hexutil.Encode(crypto.Keccak256([]byte(transferFnSignature))[:4])
paddedAddress := common.LeftPadBytes(common.HexToAddress(airdrop.Address).Bytes(), byteSize)
paddedAmount := common.LeftPadBytes(r.Amount.Bytes(), byteSize)

var txData []byte
txData = append(txData, methodID...)
txData = append(txData, paddedAddress...)
txData = append(txData, paddedAmount...)

return txData
}

func (r *Runner) getGasCosts(
func (r *Runner) getTransferTx(
ctx context.Context,
receiver common.Address,
txData []byte,
) (gasPrice *big.Int, gasLimit uint64, err error) {
gasPrice, err = r.RPC.SuggestGasPrice(ctx)
amount *big.Int,
) (tx *types.Transaction, err error) {
gasPrice, err := r.RPC.SuggestGasPrice(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to suggest gas price")
}

txOptions, err := bind.NewKeyedTransactorWithChainID(r.PrivateKey, r.ChainID)
if err != nil {
return nil, 0, errors.Wrap(err, "failed to suggest gas price")
return nil, errors.Wrap(err, "failed to get tx options")
}
txOptions.NoSend = true
txOptions.Nonce = new(big.Int).SetUint64(r.Nonce())
txOptions.GasPrice = gasPrice

gasLimit, err = r.RPC.EstimateGas(ctx, ethereum.CallMsg{
To: &receiver,
GasPrice: gasPrice,
Data: txData,
})
tx, err = r.erc20.Transfer(txOptions, receiver, amount)
if err != nil {
return nil, 0, errors.Wrap(err, "failed to estimate gas limit")
return nil, errors.Wrap(err, "failed to simulate transfer tx")
}

return
Expand Down
Loading