Skip to content

Commit

Permalink
Make eth fee rater and use server max fee rate for redemptions and re…
Browse files Browse the repository at this point in the history
…funds.
  • Loading branch information
martonp committed Feb 26, 2022
1 parent 1219455 commit 7281814
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 140 deletions.
39 changes: 30 additions & 9 deletions client/asset/eth/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,13 @@ type ethFetcher interface {
sendToAddr(ctx context.Context, addr common.Address, val uint64) (*types.Transaction, error)
transactionConfirmations(context.Context, common.Hash) (uint32, error)
sendSignedTransaction(ctx context.Context, tx *types.Transaction) error
netFeeState(ctx context.Context) (baseFees, tipCap *big.Int, err error)
}

// Check that ExchangeWallet satisfies the asset.Wallet interface.
var _ asset.Wallet = (*ExchangeWallet)(nil)
var _ asset.AccountLocker = (*ExchangeWallet)(nil)
var _ asset.FeeRater = (*ExchangeWallet)(nil)

// fundReserveType represents the various uses for which funds need to be locked:
// initiations, redemptions, and refunds.
Expand Down Expand Up @@ -883,7 +885,7 @@ func (eth *ExchangeWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Co
fundsRequired := dexeth.RedeemGas(len(form.Redemptions), contractVersion) * form.FeeSuggestion

// TODO: make sure the amount we locked for redemption is enough to cover the gas
// fees. Also unlock coins.
// fees.
tx, err := eth.node.redeem(eth.ctx, form.Redemptions, form.FeeSuggestion, contractVersion)
if err != nil {
return fail(fmt.Errorf("Redeem: redeem error: %w", err))
Expand Down Expand Up @@ -918,9 +920,9 @@ func recoverPubkey(msgHash, sig []byte) ([]byte, error) {
return pubKey.SerializeUncompressed(), nil
}

// ReserveNRedemption locks funds for redemption. It is an error if there
// ReserveNRedemptions locks funds for redemption. It is an error if there
// is insufficient spendable balance. Part of the AccountLocker interface.
func (eth *ExchangeWallet) ReserveNRedemption(n, feeRate uint64, assetVer uint32) (uint64, error) {
func (eth *ExchangeWallet) ReserveNRedemptions(n, feeRate uint64, assetVer uint32) (uint64, error) {
redeemCost := dexeth.RedeemGas(1, assetVer) * feeRate
reserve := redeemCost * n

Expand Down Expand Up @@ -951,10 +953,10 @@ func (eth *ExchangeWallet) ReReserveRedemption(req uint64) error {
return eth.lockedFunds.lock(req, redemptionReserve, eth.balance)
}

// ReserveNRefund locks funds for doing refunds. It is an error if there
// ReserveNRefunds locks funds for doing refunds. It is an error if there
// is insufficient spendable balance. Part of the AccountLocker interface.
func (eth *ExchangeWallet) ReserveNRefund(n uint64, assetVer uint32) (uint64, error) {
refundCost := dexeth.RefundGas(assetVer) * eth.gasFeeLimit
func (eth *ExchangeWallet) ReserveNRefunds(n, feeRate uint64, assetVer uint32) (uint64, error) {
refundCost := dexeth.RefundGas(assetVer) * feeRate
reserve := refundCost * n

eth.lockedFundsMtx.Lock()
Expand Down Expand Up @@ -1193,7 +1195,7 @@ func (eth *ExchangeWallet) findSecret(ctx context.Context, secretHash [32]byte,

// Refund refunds a contract. This can only be used after the time lock has
// expired.
func (eth *ExchangeWallet) Refund(_, contract dex.Bytes, _ uint64) (dex.Bytes, error) {
func (eth *ExchangeWallet) Refund(_, contract dex.Bytes, feeSuggestion uint64) (dex.Bytes, error) {
version, secretHash, err := dexeth.DecodeContractData(contract)
if err != nil {
return nil, fmt.Errorf("Refund: failed to decode contract: %w", err)
Expand All @@ -1202,7 +1204,7 @@ func (eth *ExchangeWallet) Refund(_, contract dex.Bytes, _ uint64) (dex.Bytes, e
unlockFunds := func() {
eth.lockedFundsMtx.Lock()
defer eth.lockedFundsMtx.Unlock()
err = eth.lockedFunds.unlock(eth.gasFeeLimit*dexeth.RefundGas(version), refundReserve)
err = eth.lockedFunds.unlock(feeSuggestion*dexeth.RefundGas(version), refundReserve)
if err != nil {
eth.log.Error(err)
}
Expand All @@ -1229,7 +1231,7 @@ func (eth *ExchangeWallet) Refund(_, contract dex.Bytes, _ uint64) (dex.Bytes, e
return nil, fmt.Errorf("Refund: swap with secret hash %x is not refundable", secretHash)
}

tx, err := eth.node.refund(eth.ctx, secretHash, eth.gasFeeLimit, version)
tx, err := eth.node.refund(eth.ctx, secretHash, feeSuggestion, version)
if err != nil {
return nil, fmt.Errorf("Refund: failed to call refund: %w", err)
}
Expand Down Expand Up @@ -1381,6 +1383,25 @@ func (eth *ExchangeWallet) RegFeeConfirmations(ctx context.Context, coinID dex.B
return eth.node.transactionConfirmations(ctx, txHash)
}

// FeeRate satisfies asset.FeeRater.
func (eth *ExchangeWallet) FeeRate() (uint64, error) {
base, tip, err := eth.node.netFeeState(eth.ctx)
if err != nil {
return 0, fmt.Errorf("error getting net fee state: %w", err)
}

feeRate := new(big.Int).Add(
tip,
new(big.Int).Mul(base, big.NewInt(2)))

feeRateGwei, err := dexeth.WeiToGweiUint64(feeRate)
if err != nil {
return 0, fmt.Errorf("failed to convert wei to gwei: %w", err)
}

return feeRateGwei, nil
}

func (eth *ExchangeWallet) checkPeers() {
numPeers := eth.node.peerCount()
prevPeer := atomic.SwapUint32(&eth.lastPeerCount, numPeers)
Expand Down
106 changes: 81 additions & 25 deletions client/asset/eth/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ type testNode struct {
nonce uint64
sendToAddrTx *types.Transaction
sendToAddrErr error
baseFee *big.Int
tip *big.Int
netFeeStateErr error
}

func newBalance(current, in, out uint64) *Balance {
Expand Down Expand Up @@ -117,13 +120,15 @@ func (n *testNode) lock() error {
func (n *testNode) locked() bool {
return false
}

func (n *testNode) syncProgress() ethereum.SyncProgress {
return n.syncProg
}
func (n *testNode) peerCount() uint32 {
return 1
}
func (n *testNode) netFeeState(ctx context.Context) (baseFees, tipCap *big.Int, err error) {
return n.baseFee, n.tip, n.netFeeStateErr
}

// initiate is not concurrent safe
func (n *testNode) initiate(ctx context.Context, contracts []*asset.Contract, maxFeeRate uint64, contractVer uint32) (tx *types.Transaction, err error) {
Expand Down Expand Up @@ -467,6 +472,64 @@ func TestBalance(t *testing.T) {
}
}

func TestFeeRate(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
node := &testNode{}
eth := &ExchangeWallet{
node: node,
ctx: ctx,
}

maxInt := ^int64(0)
tests := []struct {
name string
baseFee *big.Int
tip *big.Int
netFeeStateErr error
wantFeeRate uint64
wantErr bool
}{
{
name: "ok",
baseFee: big.NewInt(100e9),
tip: big.NewInt(2e9),
wantFeeRate: 202,
},
{
name: "net fee state error",
wantErr: true,
netFeeStateErr: errors.New(""),
},
{
name: "overflow error",
wantErr: true,
baseFee: big.NewInt(maxInt),
tip: big.NewInt(1),
},
}

for _, test := range tests {
node.baseFee = test.baseFee
node.tip = test.tip
node.netFeeStateErr = test.netFeeStateErr
feeRate, err := eth.FeeRate()
if test.wantErr {
if err == nil {
t.Fatalf("%v: expected error but did not get", test.name)
}
continue
}
if err != nil {
t.Fatalf("%v: unexpected error: %v", test.name, err)
}

if feeRate != test.wantFeeRate {
t.Fatalf("%v: expected fee rate %d but got %d", test.name, test.wantFeeRate, feeRate)
}
}
}

func TestRefund(t *testing.T) {
node := &testNode{
swapVers: map[uint32]struct{}{
Expand Down Expand Up @@ -501,7 +564,6 @@ func TestRefund(t *testing.T) {
contract dex.Bytes
swapStep dexeth.SwapStep
feeSuggestion uint64
gasFeeLimit uint64
originalRefundReserves uint64
wantRefundReserves uint64
isRefundable bool
Expand All @@ -518,32 +580,29 @@ func TestRefund(t *testing.T) {
swapStep: dexeth.SSInitiated,
isRefundable: true,
feeSuggestion: 100,
gasFeeLimit: 200,
originalBalance: newBalance(1e9, 0, 0),
originalRefundReserves: 1e8,
wantRefundReserves: 1e8 - 200*gasesV0.Refund,
wantRefundReserves: 1e8 - 100*gasesV0.Refund,
},
{
name: "ok v1",
contract: randomContractDataV1,
swapStep: dexeth.SSInitiated,
isRefundable: true,
feeSuggestion: 100,
gasFeeLimit: 200,
originalBalance: newBalance(1e9, 0, 0),
originalRefundReserves: 1e8,
wantRefundReserves: 1e8 - 200*gasesV1.Refund,
wantRefundReserves: 1e8 - 100*gasesV1.Refund,
},
{
name: "ok refunded",
contract: randomContractDataV0,
swapStep: dexeth.SSRefunded,
isRefundable: true,
feeSuggestion: 100,
gasFeeLimit: 200,
originalBalance: newBalance(1e9, 0, 0),
originalRefundReserves: 1e8,
wantRefundReserves: 1e8 - 200*gasesV0.Refund,
wantRefundReserves: 1e8 - 100*gasesV0.Refund,
wantZeroHash: true,
},
{
Expand All @@ -559,7 +618,6 @@ func TestRefund(t *testing.T) {
isRefundable: true,
isRefundableErr: errors.New(""),
feeSuggestion: 100,
gasFeeLimit: 200,
originalBalance: newBalance(1e9, 0, 0),
wantErr: true,
},
Expand All @@ -568,7 +626,6 @@ func TestRefund(t *testing.T) {
contract: randomContractDataV0,
isRefundable: false,
feeSuggestion: 100,
gasFeeLimit: 200,
originalBalance: newBalance(1e9, 0, 0),
wantErr: true,
},
Expand All @@ -578,7 +635,6 @@ func TestRefund(t *testing.T) {
isRefundable: true,
refundErr: errors.New(""),
feeSuggestion: 100,
gasFeeLimit: 200,
originalBalance: newBalance(1e9, 0, 0),
wantErr: true,
},
Expand All @@ -587,7 +643,6 @@ func TestRefund(t *testing.T) {
contract: []byte{},
isRefundable: true,
feeSuggestion: 100,
gasFeeLimit: 200,
originalBalance: newBalance(1e9, 0, 0),
wantErr: true,
},
Expand All @@ -600,7 +655,6 @@ func TestRefund(t *testing.T) {
node.bal = test.originalBalance
node.swapErr = test.swapErr
ss.State = test.swapStep
eth.gasFeeLimit = test.gasFeeLimit
eth.lockedFunds.refundReserves = test.originalRefundReserves

id, err := eth.Refund(nil, test.contract, test.feeSuggestion)
Expand Down Expand Up @@ -641,9 +695,9 @@ func TestRefund(t *testing.T) {
test.name, contractVer, node.lastRefund.contractVer)
}

if eth.gasFeeLimit != node.lastRefund.fee {
t.Fatalf(`%v: gas fee limit %v != used to call refund %v`,
test.name, test.feeSuggestion, eth.gasFeeLimit)
if test.feeSuggestion != node.lastRefund.fee {
t.Fatalf(`%v: fee suggestion %v != used to call refund %v`,
test.name, test.feeSuggestion, node.lastRefund.fee)
}
}

Expand Down Expand Up @@ -2451,18 +2505,20 @@ func TestRefundReserves(t *testing.T) {
eth := &ExchangeWallet{
node: node,
}
eth.gasFeeLimit = 100

var maxFeeRateV0 uint64 = 45
gasesV0 := dexeth.VersionedGases[0]

gasesV1 := &dex.Gases{Refund: 1e6}
dexeth.VersionedGases[1] = gasesV1
var maxFeeRateV1 uint64 = 50

// Lock for 3 refunds with contract version 0
v0Val, err := eth.ReserveNRefund(3, 0)
v0Val, err := eth.ReserveNRefunds(3, maxFeeRateV0, 0)
if err != nil {
t.Fatalf("ReserveNRefund error: %v", err)
t.Fatalf("ReserveNRefunds error: %v", err)
}
lockPerV0 := gasesV0.Refund * eth.gasFeeLimit
lockPerV0 := gasesV0.Refund * maxFeeRateV0
expLock := 3 * lockPerV0
if eth.lockedFunds.refundReserves != expLock {
t.Fatalf("wrong v0 locked. wanted %d, got %d", expLock, eth.lockedFunds.refundReserves)
Expand All @@ -2472,11 +2528,11 @@ func TestRefundReserves(t *testing.T) {
}

// Lock for 2 refunds with contract version 1
v1Val, err := eth.ReserveNRefund(2, 1)
v1Val, err := eth.ReserveNRefunds(2, maxFeeRateV1, 1)
if err != nil {
t.Fatalf("ReserveNRefund error: %v", err)
t.Fatalf("ReserveNRefunds error: %v", err)
}
lockPerV1 := gasesV1.Refund * eth.gasFeeLimit
lockPerV1 := gasesV1.Refund * maxFeeRateV1
v1Lock := 2 * lockPerV1
expLock += v1Lock
if eth.lockedFunds.refundReserves != expLock {
Expand Down Expand Up @@ -2529,7 +2585,7 @@ func TestRedemptionReserves(t *testing.T) {
dexeth.VersionedGases[1] = gasesV1
var maxFeeRateV1 uint64 = 50

v0Val, err := eth.ReserveNRedemption(3, maxFeeRateV0, 0)
v0Val, err := eth.ReserveNRedemptions(3, maxFeeRateV0, 0)
if err != nil {
t.Fatalf("reservation error: %v", err)
}
Expand All @@ -2544,7 +2600,7 @@ func TestRedemptionReserves(t *testing.T) {
t.Fatalf("expected value %d, got %d", lockPerV0, v0Val)
}

v1Val, err := eth.ReserveNRedemption(2, maxFeeRateV1, 1)
v1Val, err := eth.ReserveNRedemptions(2, maxFeeRateV1, 1)
if err != nil {
t.Fatalf("reservation error: %v", err)
}
Expand Down
20 changes: 11 additions & 9 deletions client/asset/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,25 +352,27 @@ type TokenMaster interface {
OpenTokenWallet(assetID uint32, settings map[string]string, tipChange func(error)) (Wallet, error)
}

// AccountLocker is a wallet in which redemptions require a wallet to have
// available balance to pay fees.
// AccountLocker is a wallet in which redemptions and refunds require a wallet
// to have available balance to pay fees.
type AccountLocker interface {
// ReserveNRedemption is used when preparing funding for an order that
// redeems to an account-based asset. The wallet will set aside the
// appropriate amount of funds so that we can redeem. It is an error
// to request funds > spendable balance.
ReserveNRedemption(n, feeRate uint64, assetVer uint32) (uint64, error)
// appropriate amount of funds so that we can redeem N swaps on the
// specified version of the asset, at the specified fee rate. It is an
// error to request funds > spendable balance.
ReserveNRedemptions(n, feeRate uint64, assetVer uint32) (uint64, error)
// ReReserveRedemption is used when reconstructing existing orders on
// startup. It is an error to request funds > spendable balance.
ReReserveRedemption(amt uint64) error
// UnlockRedemptionReserves is used to return funds reserved for redemption
// when an order is canceled or otherwise completed unfilled.
UnlockRedemptionReserves(uint64)
// ReserveNRefund is used when preparing funding for an order that
// ReserveNRefunds is used when preparing funding for an order that
// refunds to an account-based asset. The wallet will set aside the
// appropriate amount of funds so that we can refund. It is an error
// to request funds > spendable balance.
ReserveNRefund(n uint64, assetVer uint32) (uint64, error)
// appropriate amount of funds so that we can refund N swaps on the
// specified version of the asset, at the specified fee rate. It is
// an error to request funds > spendable balance.
ReserveNRefunds(n, feeRate uint64, assetVer uint32) (uint64, error)
// ReReserveRefund is used when reconstructing existing orders on
// startup. It is an error to request funds > spendable balance.
ReReserveRefund(uint64) error
Expand Down
Binary file removed client/cmd/dexc/__debug_bin
Binary file not shown.
Loading

0 comments on commit 7281814

Please sign in to comment.