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

eth/dex/client: Add initiateBatch function #1251

Merged
merged 4 commits into from
Oct 26, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 9 additions & 0 deletions client/asset/eth/rpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,15 @@ func (c *rpcclient) initiate(txOpts *bind.TransactOpts, netID int64, refundTimes
return c.es.Initiate(txOpts, big.NewInt(refundTimestamp), secretHash, *participant)
}

// initiateBatch initiates multiple swaps in the same transaction.
func (c *rpcclient) initiateBatch(txOpts *bind.TransactOpts, netID int64, initiations []swap.ETHSwapInitiation) (*types.Transaction, error) {
err := c.addSignerToOpts(txOpts, netID)
if err != nil {
return nil, err
}
return c.es.InitiateBatch(txOpts, initiations)
}

// estimateGas checks the amount of gas that is used for a function call.
func (c *rpcclient) estimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
return c.ec.EstimateGas(ctx, msg)
Expand Down
278 changes: 278 additions & 0 deletions client/asset/eth/rpcclient_harness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,284 @@ func TestInitiateGas(t *testing.T) {
fmt.Printf("Gas used for initiate: %v \n", gas)
}

func TestInitiateBatchGas(t *testing.T) {
parsedAbi, err := abi.JSON(strings.NewReader(dexeth.ETHSwapABI))
if err != nil {
t.Fatalf("unexpected error parsing abi: %v", err)
}

var previousGas uint64
for i := 1; i < 10; i++ {
initiations := make([]dexeth.ETHSwapInitiation, 0, i)
for j := 0; j < i; j++ {
var secretHash [32]byte
copy(secretHash[:], encode.RandomBytes(32))
initiations = append(initiations, dexeth.ETHSwapInitiation{
RefundTimestamp: big.NewInt(1),
SecretHash: secretHash,
Participant: participantAddr,
Value: big.NewInt(1),
})
}
data, err := parsedAbi.Pack("initiateBatch", initiations)
if err != nil {
t.Fatalf("unexpected error packing abi: %v", err)
}
msg := ethereum.CallMsg{
From: participantAddr,
To: &contractAddr,
Value: big.NewInt(int64(i)),
Gas: 0,
Data: data,
}
gas, err := participantEthClient.estimateGas(ctx, msg)
if err != nil {
t.Fatalf("unexpected error from estimateGas: %v", err)
}
fmt.Printf("Gas used for batch initiating %v swaps: %v. %v more than previous \n", i, gas, gas-previousGas)
previousGas = gas
}
}

func TestInitiateBatch(t *testing.T) {
err := ethClient.unlock(ctx, pw, simnetAcct)
if err != nil {
t.Fatal(err)
}

// Create a slice of random secret hashes that can be used in the tests and
// make sure none of them have been used yet.
numSecretHashes := 10
secretHashes := make([][32]byte, numSecretHashes)
for i := 0; i < numSecretHashes; i++ {
copy(secretHashes[i][:], encode.RandomBytes(32))
swap, err := ethClient.swap(ctx, simnetAcct, secretHashes[i])
if err != nil {
t.Fatal("unable to get swap state")
}
state := srveth.SwapState(swap.State)
if state != srveth.SSNone {
t.Fatalf("unexpected swap state: want %s got %s", srveth.SSNone, state)
}
}

now := time.Now().Unix()

intOverFlow := big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(1))
maxInt := big.NewInt(0).Sub(intOverFlow, big.NewInt(1))

tests := []struct {
name string
swaps []dexeth.ETHSwapInitiation
success bool
txValue *big.Int
}{
{
name: "1 swap ok",
success: true,
txValue: big.NewInt(1),
swaps: []dexeth.ETHSwapInitiation{
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[0],
Participant: participantAddr,
Value: big.NewInt(1),
},
},
},
{
name: "1 swap with existing hash",
success: false,
txValue: big.NewInt(1),
swaps: []dexeth.ETHSwapInitiation{
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[0],
Participant: participantAddr,
Value: big.NewInt(1),
},
},
},
{
name: "2 swaps ok",
success: true,
txValue: big.NewInt(2),
swaps: []dexeth.ETHSwapInitiation{
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[1],
Participant: participantAddr,
Value: big.NewInt(1),
},
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[2],
Participant: participantAddr,
Value: big.NewInt(1),
},
},
},
{
name: "2 swaps repeated hash",
success: false,
txValue: big.NewInt(2),
swaps: []dexeth.ETHSwapInitiation{
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[3],
Participant: participantAddr,
Value: big.NewInt(1),
},
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[3],
Participant: participantAddr,
Value: big.NewInt(1),
},
},
},
{
name: "1 swap nil refundtimestamp",
success: false,
txValue: big.NewInt(2),
swaps: []dexeth.ETHSwapInitiation{
{
RefundTimestamp: big.NewInt(0),
SecretHash: secretHashes[4],
Participant: participantAddr,
Value: big.NewInt(1),
},
},
},
{
// Preventing this used to need explicit checks before solidity 0.8, but now the
// compiler checks for integer overflows by default.
name: "integer overflow attack",
success: false,
txValue: big.NewInt(999),
swaps: []dexeth.ETHSwapInitiation{
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[5],
Participant: participantAddr,
Value: maxInt,
},
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[6],
Participant: participantAddr,
Value: big.NewInt(1000),
},
},
},
{
name: "swap with 0 value",
success: false,
txValue: big.NewInt(1000),
swaps: []dexeth.ETHSwapInitiation{
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[5],
Participant: participantAddr,
Value: big.NewInt(0),
},
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[6],
Participant: participantAddr,
Value: big.NewInt(1000),
},
},
},
{
name: "sum of swaps != msg.value",
success: false,
txValue: big.NewInt(99),
swaps: []dexeth.ETHSwapInitiation{
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[5],
Participant: participantAddr,
Value: big.NewInt(50),
},
{
RefundTimestamp: big.NewInt(now),
SecretHash: secretHashes[6],
Participant: participantAddr,
Value: big.NewInt(50),
},
},
},
}

for _, test := range tests {
originalBal, err := ethClient.balance(ctx, &simnetAddr)
if err != nil {
t.Fatalf("unexpected error for test %v: %v", test.name, err)
}

originalStates := make(map[string]srveth.SwapState)
for _, testSwap := range test.swaps {
swap, err := ethClient.swap(ctx, simnetAcct, testSwap.SecretHash)
if err != nil {
t.Fatalf("unexpected error for test %v: %v", test.name, err)
}
originalStates[hex.EncodeToString(testSwap.SecretHash[:])] = srveth.SwapState(swap.State)
}

txOpts := newTxOpts(ctx, &simnetAddr, test.txValue)
tx, err := ethClient.initiateBatch(txOpts, simnetID, test.swaps)
if err != nil {
t.Fatalf("unexpected error for test %v: %v", test.name, err)
}
spew.Dump(tx)

if err := waitForMined(t, time.Second*10, false); err != nil {
t.Fatalf("unexpected error for test %v: %v", test.name, err)
}

// It appears the receipt is only accessable after the tx is mined.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accessible

receipt, err := ethClient.transactionReceipt(ctx, tx.Hash())
if err != nil {
t.Fatalf("unexpected error for test %v: %v", test.name, err)
}
spew.Dump(receipt)

// Balance should be reduced by a certain amount depending on
// whether initiate completed successfully on-chain. If
// unsuccessful the fee is subtracted. If successful, amt is
// also subtracted.
bal, err := ethClient.balance(ctx, &simnetAddr)
if err != nil {
t.Fatalf("unexpected error for test %v: %v", test.name, err)
}
txFee := big.NewInt(0).Mul(big.NewInt(int64(receipt.GasUsed)), gasPrice)
wantBal := big.NewInt(0).Sub(originalBal, txFee)
if test.success {
wantBal.Sub(wantBal, test.txValue)
}
if bal.Cmp(wantBal) != 0 {
t.Fatalf("unexpected balance change for test %v: want %v got %v", test.name, wantBal, bal)
}

for _, testSwap := range test.swaps {
swap, err := ethClient.swap(ctx, simnetAcct, testSwap.SecretHash)
if err != nil {
t.Fatalf("unexpected error for test %v: %v", test.name, err)
}
state := srveth.SwapState(swap.State)
if test.success && state != srveth.SSInitiated {
t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, srveth.SSInitiated, state)
}

originalState := originalStates[hex.EncodeToString(testSwap.SecretHash[:])]
if !test.success && state != originalState {
t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, originalState, state)
}
}
}
}

func TestInitiate(t *testing.T) {
now := time.Now().Unix()
var secretHash [32]byte
Expand Down
2 changes: 1 addition & 1 deletion dex/networks/eth/BinRuntimeV0.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading