Skip to content

Commit

Permalink
eth/client: Redeem
Browse files Browse the repository at this point in the history
  • Loading branch information
martonp committed Dec 3, 2021
1 parent 8c36984 commit 8533e90
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 22 deletions.
36 changes: 33 additions & 3 deletions client/asset/eth/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ type ethFetcher interface {
initiate(ctx context.Context, contracts []*asset.Contract, maxFeeRate uint64, contractVer uint32) (*types.Transaction, error)
shutdown()
syncProgress() ethereum.SyncProgress
redeem(txOpts *bind.TransactOpts, redemptions []*asset.Redemption, contractVer uint32) (*types.Transaction, error)
redeem(ctx context.Context, redemptions []*asset.Redemption, maxFeeRate uint64, contractVer uint32) (*types.Transaction, error)
refund(txOpts *bind.TransactOpts, secretHash [32]byte, contractVer uint32) (*types.Transaction, error)
swap(ctx context.Context, secretHash [32]byte, contractVer uint32) (*dexeth.SwapState, error)
lock(ctx context.Context) error
Expand Down Expand Up @@ -672,8 +672,38 @@ func (eth *ExchangeWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin

// Redeem sends the redemption transaction, which may contain more than one
// redemption.
func (*ExchangeWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, uint64, error) {
return nil, nil, 0, asset.ErrNotImplemented
func (eth *ExchangeWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, uint64, error) {
fail := func(err error) ([]dex.Bytes, asset.Coin, uint64, error) {
return nil, nil, 0, err
}

if len(form.Redemptions) == 0 {
return fail(errors.New("Redeem: must be called with at least 1 redemption"))
}

inputs := make([]dex.Bytes, 0, len(form.Redemptions))
var redeemedValue uint64
for _, redemption := range form.Redemptions {
var secretHash [32]byte
copy(secretHash[:], redemption.Spends.SecretHash)
swapData, err := eth.node.swap(eth.ctx, secretHash, form.AssetVersion)
if err != nil {
return nil, nil, 0, fmt.Errorf("Redeem: error finding swap state: %w", err)
}
redeemedValue += swapData.Value
inputs = append(inputs, redemption.Spends.Coin.ID())
}
outputCoin := eth.createAmountCoin(redeemedValue)
fundsRequired := dexeth.RedeemGas(len(form.Redemptions), form.AssetVersion) * form.FeeSuggestion

// TODO: make sure the amount we locked for redemption is enough to cover the gas
// fees.
_, err := eth.node.redeem(eth.ctx, form.Redemptions, form.FeeSuggestion, form.AssetVersion)
if err != nil {
return fail(fmt.Errorf("Redeem: redeem error: %w", err))
}

return inputs, outputCoin, fundsRequired, nil
}

// SignMessage signs the message with the private key associated with the
Expand Down
201 changes: 197 additions & 4 deletions client/asset/eth/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type testNode struct {
swapMap map[[32]byte]*dexeth.SwapState
swapErr error
initErr error
redeemErr error
nonce uint64
}

Expand Down Expand Up @@ -134,10 +135,32 @@ func (n *testNode) initiate(ctx context.Context, contracts []*asset.Contract, ma
Nonce: n.nonce,
}), nil
}
func (n *testNode) redeem(opts *bind.TransactOpts, redemptions []*asset.Redemption, contractVer uint32) (*types.Transaction, error) {
return nil, nil

func (n *testNode) redeem(ctx context.Context, redemptions []*asset.Redemption, maxFeeRate uint64, contractVer uint32) (*types.Transaction, error) {
if n.redeemErr != nil {
return nil, n.redeemErr
}
/*baseTx := &types.DynamicFeeTx{
Nonce: n.nonce,
GasFeeCap: opts.GasFeeCap,
GasTipCap: opts.GasTipCap,
Gas: opts.GasLimit,
Value: opts.Value,
Data: []byte{},
}*/
//tx := types.NewTx(baseTx)
n.nonce++
// n.lastRedemption = redeemTx{
// redemptions: redemptions,
// hash: tx.Hash(),
// opts: opts,
// tx: tx,
// }
return types.NewTx(&types.DynamicFeeTx{
Nonce: n.nonce,
}), nil
}
func (n *testNode) refund(opts *bind.TransactOpts, secretHash [32]byte, serverVer uint32) (*types.Transaction, error) {
func (n *testNode) refund(txOpts *bind.TransactOpts, secretHash [32]byte, contractVer uint32) (*types.Transaction, error) {
return nil, nil
}
func (n *testNode) swap(ctx context.Context, secretHash [32]byte, contractVer uint32) (*dexeth.SwapState, error) {
Expand All @@ -154,7 +177,6 @@ func (n *testNode) swap(ctx context.Context, secretHash [32]byte, contractVer ui
}
return swap, nil
}

func (n *testNode) signData(addr common.Address, data []byte) ([]byte, error) {
if n.signDataErr != nil {
return nil, n.signDataErr
Expand Down Expand Up @@ -1138,6 +1160,177 @@ func TestPreRedeem(t *testing.T) {
}
}

func TestRedeem(t *testing.T) {
node := &testNode{
swapVers: map[uint32]struct{}{
0: {},
},
swapMap: make(map[[32]byte]*dexeth.SwapState),
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
eth := &ExchangeWallet{
node: node,
ctx: ctx,
log: tLogger,
}
addSwapToSwapMap := func(secretHash [32]byte, value uint64, step dexeth.SwapStep) {
swap := dexeth.SwapState{
BlockHeight: 1,
LockTime: time.Now(),
Initiator: common.HexToAddress("0x2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27"),
Participant: common.HexToAddress("B6De8BB5ed28E6bE6d671975cad20C03931bE981"),
Value: value,
State: step,
}
node.swapMap[secretHash] = &swap
}

numSecrets := 3
secrets := make([][32]byte, 0, numSecrets)
secretHashes := make([][32]byte, 0, numSecrets)
for i := 0; i < numSecrets; i++ {
var secret [32]byte
copy(secret[:], encode.RandomBytes(32))
secretHash := sha256.Sum256(secret[:])
secrets = append(secrets, secret)
secretHashes = append(secretHashes, secretHash)
}

addSwapToSwapMap(secretHashes[0], 1e9, dexeth.SSInitiated)
addSwapToSwapMap(secretHashes[1], 1e9, dexeth.SSInitiated)

tests := []struct {
name string
form asset.RedeemForm
redeemErr error
expectError bool
}{
{
name: "ok",
expectError: false,
form: asset.RedeemForm{
Redemptions: []*asset.Redemption{
{
Spends: &asset.AuditInfo{
SecretHash: secretHashes[0][:],
Coin: &coin{
id: encode.RandomBytes(32),
},
},
Secret: secrets[0][:],
},
{
Spends: &asset.AuditInfo{
SecretHash: secretHashes[1][:],
Coin: &coin{
id: encode.RandomBytes(32),
},
},
Secret: secrets[1][:],
},
},
FeeSuggestion: 100,
AssetVersion: 0,
},
},
{
name: "redeem error",
redeemErr: errors.New(""),
expectError: true,
form: asset.RedeemForm{
Redemptions: []*asset.Redemption{
{
Spends: &asset.AuditInfo{
SecretHash: secretHashes[0][:],
Coin: &coin{
id: encode.RandomBytes(32),
},
},
Secret: secrets[0][:],
},
},
FeeSuggestion: 200,
AssetVersion: 0,
},
},
{
name: "swap not found in contract",
expectError: true,
form: asset.RedeemForm{
Redemptions: []*asset.Redemption{
{
Spends: &asset.AuditInfo{
SecretHash: secretHashes[2][:],
Coin: &coin{
id: encode.RandomBytes(32),
},
},
Secret: secrets[2][:],
},
},
FeeSuggestion: 100,
AssetVersion: 0,
},
},
{
name: "empty redemptions slice error",
expectError: true,
form: asset.RedeemForm{
Redemptions: []*asset.Redemption{},
FeeSuggestion: 100,
AssetVersion: 0,
},
},
}

for _, test := range tests {
node.redeemErr = test.redeemErr

ins, out, fees, err := eth.Redeem(&test.form)
if test.expectError {
if err == nil {
t.Fatalf("%v: expected error", test.name)
}
continue
}
if err != nil {
t.Fatalf("%v: unexpected error: %v", test.name, err)
}

if len(ins) != len(test.form.Redemptions) {
t.Fatalf("%v: expected %d inputs but got %d",
test.name, len(test.form.Redemptions), len(ins))
}

// Check fees returned from Redeem are as expected
expectedGas := dexeth.RedeemGas(len(test.form.Redemptions), 0)
expectedFees := expectedGas * test.form.FeeSuggestion
if fees != expectedFees {
t.Fatalf("%v: expected fees %d, but got %d", test.name, expectedFees, fees)
}

var totalSwapValue uint64
for i, redemption := range test.form.Redemptions {
coinID := redemption.Spends.Coin.ID()
if !bytes.Equal(coinID, ins[i]) {
t.Fatalf("%v: expected input %x to equal coin id %x",
test.name, coinID, ins[i])
}

var secretHash [32]byte
copy(secretHash[:], redemption.Spends.SecretHash)
swap := node.swapMap[secretHash]
totalSwapValue += swap.Value
}

if out.Value() != totalSwapValue {
t.Fatalf("expected coin value to be %d but got %d",
totalSwapValue, out.Value())
}
}
}

func TestMaxOrder(t *testing.T) {
gases := dexeth.VersionedGases[0]

Expand Down
13 changes: 7 additions & 6 deletions client/asset/eth/nodeclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ func (n *nodeClient) addSignerToOpts(txOpts *bind.TransactOpts) error {

// initiate initiates multiple swaps in the same transaction.
func (n *nodeClient) initiate(ctx context.Context, contracts []*asset.Contract, maxFeeRate uint64, contractVer uint32) (tx *types.Transaction, err error) {
gas := dexeth.InitGas(len(contracts), 0)
gas := dexeth.InitGas(len(contracts), contractVer)
var val uint64
for _, c := range contracts {
val += c.Value
Expand Down Expand Up @@ -417,12 +417,13 @@ func (n *nodeClient) estimateRefundGas(ctx context.Context, secretHash [32]byte,
})
}

// redeem redeems a swap contract. The redeemer will be the account at txOpts.From.
// Any on-chain failure, such as this secret not matching the hash, will not cause
// this to error.
func (n *nodeClient) redeem(txOpts *bind.TransactOpts, redemptions []*asset.Redemption, contractVer uint32) (tx *types.Transaction, err error) {
// redeem redeems a swap contract. Any on-chain failure, such as this secret not
// matching the hash, will not cause this to error.
func (n *nodeClient) redeem(ctx context.Context, redemptions []*asset.Redemption, maxFeeRate uint64, contractVer uint32) (tx *types.Transaction, err error) {
gas := dexeth.RedeemGas(len(redemptions), contractVer)
txOpts, _ := n.txOpts(ctx, 0, gas, dexeth.GweiToWei(maxFeeRate))
if err := n.addSignerToOpts(txOpts); err != nil {
return nil, err
return nil, fmt.Errorf("addSignerToOpts error: %w", err)
}
return tx, n.withcontractor(contractVer, func(c contractor) error {
tx, err = c.redeem(txOpts, redemptions)
Expand Down
10 changes: 2 additions & 8 deletions client/asset/eth/nodeclient_harness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -867,9 +867,6 @@ func testRedeem(t *testing.T) {
}

for _, test := range tests {
numSwaps := len(test.swaps)
txOpts, _ := ethClient.txOpts(ctx, uint64(numSwaps), dexeth.InitGas(numSwaps, 0), nil)

for i := range test.swaps {
swap, err := ethClient.swap(ctx, bytesToArray(test.swaps[i].SecretHash), 0)
if err != nil {
Expand Down Expand Up @@ -907,8 +904,6 @@ func testRedeem(t *testing.T) {
t.Fatalf("%s: balance error: %v", test.name, err)
}
baseFee, _, _ := test.redeemerClient.netFeeState(ctx)
txOpts, _ = test.redeemerClient.txOpts(ctx, 0, dexeth.RedeemGas(numSwaps, 0), nil)

for i, redemption := range test.redemptions {
expected := test.isRedeemable[i]
isRedeemable, err := test.redeemerClient.isRedeemable(bytesToArray(redemption.Spends.SecretHash), bytesToArray(redemption.Secret), 0)
Expand All @@ -920,7 +915,7 @@ func testRedeem(t *testing.T) {
}
}

tx, err := test.redeemerClient.redeem(txOpts, test.redemptions, 0)
tx, err := test.redeemerClient.redeem(ctx, test.redemptions, maxFeeRate, 0)
if err != nil {
t.Fatalf("%s: redeem error: %v", test.name, err)
}
Expand Down Expand Up @@ -1085,8 +1080,7 @@ func testRefund(t *testing.T) {
if err := waitForMined(t, time.Second*8, false); err != nil {
t.Fatalf("%s: pre-redeem mining error: %v", test.name, err)
}
txOpts, _ := participantEthClient.txOpts(ctx, 0, dexeth.RedeemGas(1, 0), nil)
_, err := participantEthClient.redeem(txOpts, []*asset.Redemption{newRedeem(secret, secretHash)}, 0)
_, err := participantEthClient.redeem(ctx, []*asset.Redemption{newRedeem(secret, secretHash)}, maxFeeRate, 0)
if err != nil {
t.Fatalf("%s: redeem error: %v", test.name, err)
}
Expand Down
3 changes: 3 additions & 0 deletions client/asset/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@ type RedeemForm struct {
// suggestion in any way, but obviously fees that are too low may result in
// the redemption getting stuck in mempool.
FeeSuggestion uint64
// AssetVersion is the swap protocol version, which may indicate a specific
// contract or form of contract.
AssetVersion uint32
}

// Order is order details needed for FundOrder.
Expand Down
1 change: 1 addition & 0 deletions client/core/trade.go
Original file line number Diff line number Diff line change
Expand Up @@ -1677,6 +1677,7 @@ func (c *Core) redeemMatchGroup(t *trackedTrade, matches []*matchTracker, errs *
coinIDs, outCoin, fees, err := redeemWallet.Redeem(&asset.RedeemForm{
Redemptions: redemptions,
FeeSuggestion: feeSuggestion,
AssetVersion: t.metaData.ToVersion,
})
// If an error was encountered, fail all of the matches. A failed match will
// not run again on during ticks.
Expand Down
2 changes: 1 addition & 1 deletion dex/networks/eth/txdata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func TestParseRedeemData(t *testing.T) {
}
calldata, err := PackRedeemData(redemptions)
if err != nil {
t.Fatalf("unale to pack abi: %v", err)
t.Fatalf("unable to pack abi: %v", err)
}
redeemCallData := mustParseHex("f4fd17f9000000000000000000000000000000000" +
"000000000000000000000000000002000000000000000000000000000000000000" +
Expand Down

0 comments on commit 8533e90

Please sign in to comment.