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 swap contract v1 #1426

Closed
wants to merge 5 commits into from
Closed

Conversation

buck54321
Copy link
Member

@buck54321 buck54321 commented Jan 19, 2022

I was just playing with this trying to minimize fees. I think it's sound, but there are some tradeoffs and some new edges that need exploring. Leaving in draft for the foreseeable future and focusing on token stuff.

Don't store contract data on-chain, just commit to it in the key of
the swaps map, which maps to a "swap record", 32 bytes in length,
with a special interpretation scheme that allows us to track the
swap status. This reduces fees by a factor of ~2 for a single-match
round-trip trade, which would be similar to e.g. ETH-USDC or other
token trade. For multi-match trades, the savings are even better,
asymptoting at ~28% of the v0 fees. A single-match round trip trade
uses ~ 93k gas, which is notably less than a trade on UniSwap and
other Ethereum trading platforms which are typically > 135k, I think.

@buck54321 buck54321 force-pushed the contract-versions branch 2 times, most recently from d3fc284 to 1f0da0d Compare January 19, 2022 01:23
Copy link
Member

@JoeGruffins JoeGruffins left a comment

Choose a reason for hiding this comment

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

Some comments on the contract.

Comment on lines 124 to 125
// Is this needed?
// require(msg.sender == c.initiator)
Copy link
Member

@JoeGruffins JoeGruffins Jan 19, 2022

Choose a reason for hiding this comment

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

It would be required for refund, but as far as I know this would just lead to the initiator possibly shooting themselves in the foot.

Copy link
Member

Choose a reason for hiding this comment

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

This got me thinking so I made #1427

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, it doesn't seem strictly necessary to me. I see this as limiting to 1) third-party services, and 2) privacy-focused clients.

Comment on lines 53 to 63
// contractKey generates a key hash which commits to the contract data. The
// generated hash is used as a key in the swaps map.
function contractKey(Contract calldata c) public pure returns (bytes32) {
return sha256(abi.encodePacked(c.secretHash, c.initiator, c.participant, c.value, c.refundTimestamp));
}
Copy link
Member

Choose a reason for hiding this comment

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

This obfuscates things a bit. All of this information is needed to reconstruct the key where it was just the secret hash before. If any is lost the swap wouldn't even be refundable. Say you lost all data and had to reseed. With version zero you could look at all the swaps and probably find your address as initiator with status refundable and wouldn't need the secret anymore. With this version, I guess the server would need to help the user out if they needed to refund in that scenario. Just playing devil's advocate here, it is a good idea to save space.

Copy link
Member Author

Choose a reason for hiding this comment

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

With version zero you could look at all the swaps and probably find your address as initiator with status refundable and wouldn't need the secret anymore.

Can you though? Swaps are keyed by secret hash, and there is no getter for all the swaps. If you nuke your db during settlement, aren't all bets are off? I feel like this is a just a general limitation of p2sh contracts that involve two parties.

Copy link
Member

Choose a reason for hiding this comment

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

there is no getter for all the swaps

I think you can get all the swaps, it's public. I know I did in the begging while testing.

Copy link
Member

Choose a reason for hiding this comment

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

I think this used to return all the swaps

// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2.
//
// Solidity: function swaps(bytes32 ) view returns(bytes32 secret, uint256 value, uint256 initBlockNumber, uint256 refundBlockTimestamp, address initiator, address participant, uint8 state)
func (_ETHSwap *ETHSwapCaller) Swaps(opts *bind.CallOpts, arg0 [32]byte) (struct {

But looks like it doesn't now?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we don't even need the Swap getter now?

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I think you're right and I'm miss-remembering: https://ethereum.stackexchange.com/questions/15337/can-we-get-all-elements-stored-in-a-mapping-in-the-contract

So, one wouldn't be able to find their swap without the key/secret hash

Comment on lines 34 to 36
// else if (uint256(record) < block.number && sha256(record) != contract.secret):
// contract is initiated and redeemable by the participant with the secret.
// else if (sha256(record) == contract.secret): contract has been redeemed
Copy link
Member

Choose a reason for hiding this comment

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

contract.secret is contract.secretHash?

If secret was less than the block number and hashed to the secret hash, it would never be redeemable?

bytes32 record = swaps[k];
require(record == bytes32(0), "swap not empty");

swaps[k] = bytes32(block.number);
Copy link
Member

Choose a reason for hiding this comment

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

I think you need to make sure this does not hash to secret hash. One could predict the block this gets mined (or is this the block before? I've forgotten).

if (secretValidates(record, c.secretHash)) {
return State.Redeemed;
}
// Is it worth checking whether blockNum < block.number?
Copy link
Member

Choose a reason for hiding this comment

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

I would say no.

// To be redeemable, the record needs to represent a valid block
// number.
require(blockNum > 0 && blockNum < block.number, "unfilled swap");
// require(blockNum >> 32 == 0, "invalid swap format");
Copy link
Member

Choose a reason for hiding this comment

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

Commented out, but does ethereum have this limitation of a uint32 block number?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, but it'll take a couple hundred years to get to that block height. This comment is left over from an earlier iteration on the record scheme, but blockNum < block.number is better.

Comment on lines 332 to 333
// This one breaks contractor, since we need the entire *asset.Contract, not just
// the secret hash.
Copy link
Member

@chappjc chappjc Sep 5, 2022

Choose a reason for hiding this comment

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

There are quite a few broken like this, including swap, which is used all over. e.g. https://github.com/decred/dcrdex/pull/1638/files#diff-a45b0f60d5cf2f6825eb9e9ed1f173fbf1df9eb53b09914ce6b17e42074653e5R101

I'm confused about how we're going to adapt if we can't look up the Swap struct by secret hash any more.

It also becomes super hard to use the refund-by-anyone path we are relying on as a backup. When we create the swap we can't a pre-signed refund, so we have been logging the secret hash so the user can just (e.g.) take that to etherscan and refund with metamask.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm confused about how we're going to adapt if we can't look up the Swap struct by secret hash any more.

We could do it with the Swaps and the ContractKey methods. We would just need to interpret ourselves. But I've added a little more detail to the return from state (renamed to status) to make things easier. Happy to go back if you're concerned with the contract change.

Copy link
Member Author

Choose a reason for hiding this comment

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

It also becomes super hard to use the refund-by-anyone path we are relying on as a backup. When we create the swap we can't a pre-signed refund, so we have been logging the secret hash so the user can just (e.g.) take that to etherscan and refund with metamask.

I'll look into a solution, but I don't see any reason that something like this should stop us.

@@ -326,7 +330,7 @@ type Wallet interface {
// NOTE: This could potentially be a long and expensive operation if
// performed long after the swap is broadcast; might be better executed from
// a goroutine.
FindRedemption(ctx context.Context, coinID, contract dex.Bytes) (redemptionCoin, secret dex.Bytes, err error)
FindRedemption(ctx context.Context, coinID, contractData dex.Bytes, deets *dex.SwapContractDetails) (redemptionCoin, secret dex.Bytes, err error)
Copy link
Member Author

Choose a reason for hiding this comment

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

I was on the fence about adding these arguments, because I think we could probably work it out without them, through the tx cache or something. But all of the info in the SwapContractDetails is required to be saved by the client already, so it actually seems more on-model to have Core supply it. Open to suggestions though.

Copy link
Member

@chappjc chappjc Sep 6, 2022

Choose a reason for hiding this comment

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

I agree. We should not rely on the backend somehow having recorded it previously. I think all the method should be able to do their job with the inputs plus what they find on-chain.

es, err := erc20v0.NewERC20Swap(swapContract.Address, be)
es, err := erc20v1.NewERC20Swap(swapContract.Address, be)
Copy link
Member Author

Choose a reason for hiding this comment

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

I had always intended for the server to only support a single version at a time, and I still do. But there's probably an argument for making it switchable via configuration. I don't know.

Copy link
Member

@JoeGruffins JoeGruffins Sep 9, 2022

Choose a reason for hiding this comment

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

Although it would seem crazy to us, a user will update in the middle of a swap. Or perhaps a redeem or refund they forgot about. It would be responsible to continue to support old versions and deprecate slowly.

Copy link
Member

Choose a reason for hiding this comment

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

I would expect the client would be able to refund or auto-redeem those on their own.

Slightly related, which I am still intending to address myself: #1583

Copy link
Member

@JoeGruffins JoeGruffins Sep 12, 2022

Choose a reason for hiding this comment

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

Oh, this is the server, I wanted to make this comment on the client.

Copy link
Member

Choose a reason for hiding this comment

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

Oh yeah, we are handling both versions for the client. Please ignore my comment.

Copy link
Member

@JoeGruffins JoeGruffins Sep 12, 2022

Choose a reason for hiding this comment

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

I guess markets will just need to be suspended for makerlocktime to ensure no new initiations? Or are clients handling this fine already? And hopefully refunds will still be ok, I think ok. Should be ok.

Comment on lines 677 to +709
// Spends is the AuditInfo for the swap output being spent.
Spends *AuditInfo
// SwapDetails is the swap details.
SwapDetails *dex.SwapContractDetails
Copy link
Member Author

@buck54321 buck54321 Sep 5, 2022

Choose a reason for hiding this comment

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

I'd like to refactor this later. SwapContractDetails encodes all of AuditInfo except for the Contract field. But I didn't want to mess with the btc/dcr interfaces too much in the PR.

Comment on lines -205 to -236
// Uninitiated state is zero confs. It could still be in mempool.
// It is important to only trust confirmations according to the
// swap contract. Until there are confirmations we cannot be sure
// that initiation happened successfully.
if swap.State == dexeth.SSNone {
// Assume the tx still has a chance of being mined.
return 0, nil
}
Copy link
Member Author

Choose a reason for hiding this comment

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

It wasn't quite clear to me why we wouldn't return CoinNotFoundError here. We have the latency queue running for (Backend).Confirmations in (Swapper).processInit.

Copy link
Member

Choose a reason for hiding this comment

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

I don't remember. CoinNotFoundError is probably correct.

Copy link
Member

Choose a reason for hiding this comment

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

I believe it's because the contract state will not be reflecting txns in txpool. As such, there is ambiguity here about whether it's really not found or if it is in fact in txpool. What is the problem with returning 0, nil? That would be correct for a transaction not yet mined.

Comment on lines 188 to -229
func (c *swapCoin) Confirmations(ctx context.Context) (int64, error) {
swap, err := c.backend.node.swap(ctx, c.backend.assetID, c.secretHash)
if err != nil {
return -1, err
}

// Uninitiated state is zero confs. It could still be in mempool.
Copy link
Member Author

Choose a reason for hiding this comment

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

I've moved these functions away from contract inspection and towards more transaction inspection. I think it simplifies things. Any reason I can't do that?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I don't recall why the Confirmations methods were so contract state oriented. Neither do I remember why dexeth.SSNone should not be a CoinNotFoundError. Perhaps @JoeGruffins recalls.

Copy link
Member

@JoeGruffins JoeGruffins Sep 9, 2022

Choose a reason for hiding this comment

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

I wanted to ensure that this info comes from the contract and not our assumptions about the tx. Especially with the recent redemptions pr we may be looking at a bogus transaction. (I realize this is the init one but still)

If you are sure we are not then fine, but I'm not sure we couldn't have a tx that passed our verification but does not do what we expect. Our txdata parsing is basically a hack, we had to copy paste from geth internals. I do not think it is intended to be done this way.

At least there needs to be another check besides confirmations that checks status is ok, as in it did what we expected onchain. But, I hope we can not trust the transaction except for the basic sanity check we were doing and start looking at the contract here.

Copy link
Member

@chappjc chappjc Sep 9, 2022

Choose a reason for hiding this comment

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

It's certainly not the eth way. Things are meant to replaceable, and the focus is supposed to be on state change, and contract interactions.

Is there an reason for just checking the txid and not getting confs from the contract?


On the topic Joe brings up, I'd feel better about doing less tx inspection with the hacked call data parsing if possible.

I worry we're already dug in on something we shouldn't be doing as well.

@@ -119,6 +119,7 @@ var (
rpcWalletDefinition,
electrumWalletDefinition,
},
ProtocolVersions: []uint32{0},
Copy link
Member

Choose a reason for hiding this comment

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

line 17 has version = 1. Is this yet another version or the same?

Copy link
Member Author

Choose a reason for hiding this comment

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

// ProtocolVersions is the Wallet's accepted server version numbers.

Copy link
Member

@chappjc chappjc Sep 6, 2022

Choose a reason for hiding this comment

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

And what was version? That's exactly what I thought it was.

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right. Sorry. Core was using it like you say. Should I get rid of Version?

Copy link
Member

Choose a reason for hiding this comment

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

Whatever you prefer. I was just confused that there was something else being tracked here.

return false, time.Time{}, fmt.Errorf("error extracting contract locktime: %w", err)
}
contractExpiry := time.Unix(int64(locktime), 0).UTC()
func (btc *baseWallet) LocktimeExpired(_ context.Context, deets *dex.SwapContractDetails) (bool, time.Time, error) {
Copy link
Member

@chappjc chappjc Sep 6, 2022

Choose a reason for hiding this comment

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

One thing I really hate is when we have function arguments with way more info than is needed for a sensible implementation.

For BTC, say you're trying to use LocktimeExpored or Refund or whatever, so you're prepping to call the baseWallet method... you think, what fields of SwapContractDetails does it really need?
I get why. We're gravitating toward an interface that fits all assets and ETH is getting very different now.
But what can we do to avoid this? Obviously docs can be very clear for each asset to call out the used data, at a minimum.

Copy link
Member Author

Choose a reason for hiding this comment

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

what fields of SwapContractDetails does it really need?

I'm with you on the API, but I'm not adding any more than is needed here. We could cache the data internally, but trying to avoid that.

Copy link
Member

Choose a reason for hiding this comment

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

I mean it's way more than needed for BTC.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why not stick all the data from SwapContractDetails into the contract dex.Bytes?

Copy link
Member

Choose a reason for hiding this comment

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

That sounds appealing to me too.

Don't store contract data on-chain, just commit to it in the key of
the swaps map, which maps to a "swap record", 32 bytes in length,
with a special interpretation scheme that allows us to track the
swap status. This reduces fees by a factor of ~2 for a single lot
round-trip trade, which would be similar to e.g. ETH-USDC or other
token trade. For many-lot trades, the savings are even better,
asymptoting at ~28% of the v0 fees. A single-lot round trip trade
uses ~ 93k gas, which is notably less than a trade on UniSwap and
other Ethereum trading platforms.
Add support for new contracts through server/asset/eth and
client/asset/eth. Requires some modifications to client/core
and client/asset to add more swap info to some methods.

Differentiate wallet version from supported server versions.
@buck54321 buck54321 marked this pull request as ready for review September 6, 2022 23:49
Comment on lines +210 to +211
var secretHash [32]byte
copy(secretHash[:], contract.SecretHash)
Copy link
Member

Choose a reason for hiding this comment

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

I see this copy a lot. Maybe contract.SecretHash could just be a [32]byte? Underneath it would still copy but be a few lines less code.


func newV1Contractor(net dex.Network, acctAddr common.Address, cb bind.ContractBackend) (contractor, error) {
contractAddr, exists := dexeth.ContractAddresses[1][net]
if !exists || contractAddr == (common.Address{}) {
Copy link
Member

Choose a reason for hiding this comment

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

Do the () do something here?

value = dexeth.GweiToWei(uint64(n))
}

return c.estimateGas(ctx, value, "initiate", initiations)
Copy link
Member

Choose a reason for hiding this comment

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

nit: The strings "initiate", "redeem", "refund" are used quite a bit, could be constants to avoid spelling errors.

}
var secretHash [32]byte
copy(secretHash[:], deets.SecretHash)
return sha256.Sum256(secret[:]) == secretHash, nil
Copy link
Member

Choose a reason for hiding this comment

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

I would think it's a little late to do this check. We should be certain before the swap gets this far.

// readOnlyCallOpts is the CallOpts used for read-only contract method calls.
func readOnlyCallOpts(ctx context.Context) *bind.CallOpts {
return &bind.CallOpts{
Pending: true,
Copy link
Member

Choose a reason for hiding this comment

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

I think all or none of the call opts should have pending set true. Others in this file are not set.


// To be redeemable, the record needs to represent a valid block
// number.
require(blockNum > 0 && blockNum < block.number, "unfilled swap");
Copy link
Member

Choose a reason for hiding this comment

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

uninitiated or refunded swap. It is also most redeemed swaps because secret would be a number > block.number

I forget now but probably fine to use the isRedeemable in place of this and the check below.

Comment on lines +135 to +136
record = bytes32(block.number);
require(!secretValidates(record, c.secretHash), "hash collision");
Copy link
Member

Choose a reason for hiding this comment

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

Although just as impossible as this collision, the secret can also not be RefundRecord. You could find out what that hashes to and do a straight != with the constant and record if you wanted to.

Comment on lines +212 to +213
// Is this swap initialized?
require(blockNum > 0 && blockNum <= block.number, "swap not active");
Copy link
Member

Choose a reason for hiding this comment

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

I think this makes the refunded check not reachable if refunded. It also stops most checks for is redeemed. Both of which are fine, but can remove the explicit refund check I guess, or move it and the redeem check up if the error is important.

Comment on lines +21 to +25
const (
initiateFuncName = "initiate"
redeemFuncName = "redeem"
refundFuncName = "refund"
)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe export these? they are used a bunch in the clients.

}
args := decoded.inputs
// Any difference in number of args and types than what we expect
// should be caught by parseCallData, but checking again anyway.
Copy link
Member

Choose a reason for hiding this comment

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

parseCallData -> ParseCallData

Comment on lines +48 to +49
// specific version of the swap contract. It returns the the list of initiations
// done in the call and errors if the call data does not call initiate initiate
Copy link
Member

@JoeGruffins JoeGruffins Sep 9, 2022

Choose a reason for hiding this comment

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

the twice in It returns the the and initiate twice in does not call initiate initiate

Comment on lines +259 to +264
// This is done for the compiler to ensure that the type defined above and
// swapv0.ETHSwapRedemption are the same, other than the tags.
if len(redemptions) > 0 {
// Why can't I do ETHSwapRedemption directly?
_ = swapv1.ETHSwapContract(redemptions[0].C)
}
Copy link
Member

Choose a reason for hiding this comment

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

I guess the nested types cannot be casted https://go.dev/play/p/8PBsInPWbCz

It looks like this works:

	if len(redemptions) > 0 {
		_ = swapv1.ETHSwapRedemption(struct {
			C      swapv1.ETHSwapContract
			Secret [32]uint8
		}{
			C:      swapv1.ETHSwapContract(redemptions[0].C),
			Secret: redemptions[0].Secret,
		})
	}

Could you make tests like there are for v1?

Comment on lines +300 to +306
contract, ok := args[0].value.(struct {
SecretHash [32]byte `json:"secretHash"`
Initiator common.Address `json:"initiator"`
RefundTimestamp uint64 `json:"refundTimestamp"`
Participant common.Address `json:"participant"`
Value uint64 `json:"value"`
})
Copy link
Member

Choose a reason for hiding this comment

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

Could add the check like the others that these are the same fields as swapv1.ETHSwapContract

Value uint64 `json:"value"`
})
if !ok {
return nil, fmt.Errorf("expected first arg of type [32]byte but got %T", args[0].value)
Copy link
Member

Choose a reason for hiding this comment

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

not a [32]byte but swapv1.ETHSwapContract I guess

Comment on lines -94 to -119
if err == asset.CoinNotFoundError {
// If the coin is not found, check to see if the swap has been
// redeemed by another transaction.
contractVer, secretHash, err := dexeth.DecodeContractData(contractData)
if err != nil {
return nil, err
}
be.log.Warnf("redeem coin with ID %x for secret hash %x was not found", coinID, secretHash)
swapState, err := be.node.swap(be.ctx, be.assetID, secretHash)
if err != nil {
return nil, err
}
if swapState.State != dexeth.SSRedeemed {
return nil, asset.CoinNotFoundError
}
bc = &baseCoin{
backend: be,
secretHash: secretHash,
contractVer: contractVer,
}
return &redeemCoin{
baseCoin: bc,
secret: swapState.Secret,
}, nil
} else if err != nil {
return nil, err
Copy link
Member

Choose a reason for hiding this comment

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

Maybe this was a rebase mixup? I think we need this because of the redemption pr recently. The client may have redeemed with a different tx.

Comment on lines 188 to -229
func (c *swapCoin) Confirmations(ctx context.Context) (int64, error) {
swap, err := c.backend.node.swap(ctx, c.backend.assetID, c.secretHash)
if err != nil {
return -1, err
}

// Uninitiated state is zero confs. It could still be in mempool.
Copy link
Member

@JoeGruffins JoeGruffins Sep 9, 2022

Choose a reason for hiding this comment

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

I wanted to ensure that this info comes from the contract and not our assumptions about the tx. Especially with the recent redemptions pr we may be looking at a bogus transaction. (I realize this is the init one but still)

If you are sure we are not then fine, but I'm not sure we couldn't have a tx that passed our verification but does not do what we expect. Our txdata parsing is basically a hack, we had to copy paste from geth internals. I do not think it is intended to be done this way.

At least there needs to be another check besides confirmations that checks status is ok, as in it did what we expected onchain. But, I hope we can not trust the transaction except for the basic sanity check we were doing and start looking at the contract here.

Comment on lines -205 to -236
// Uninitiated state is zero confs. It could still be in mempool.
// It is important to only trust confirmations according to the
// swap contract. Until there are confirmations we cannot be sure
// that initiation happened successfully.
if swap.State == dexeth.SSNone {
// Assume the tx still has a chance of being mined.
return 0, nil
}
Copy link
Member

Choose a reason for hiding this comment

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

I don't remember. CoinNotFoundError is probably correct.

es, err := erc20v0.NewERC20Swap(swapContract.Address, be)
es, err := erc20v1.NewERC20Swap(swapContract.Address, be)
Copy link
Member

@JoeGruffins JoeGruffins Sep 9, 2022

Choose a reason for hiding this comment

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

Although it would seem crazy to us, a user will update in the middle of a swap. Or perhaps a redeem or refund they forgot about. It would be responsible to continue to support old versions and deprecate slowly.

Copy link
Contributor

@martonp martonp left a comment

Choose a reason for hiding this comment

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

Looks good overall. I think the API would be more simple if all the dex.SwapContractDetails stuff was just in the contract bytes. All of the information in there is available when the contract bytes are created in Swap.

// pragma should be as specific as possible to allow easier validation.
pragma solidity = 0.8.15;

// ETHSwap creates a contract to be deployed on an ethereum network. In
Copy link
Contributor

Choose a reason for hiding this comment

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

ERC20Swap creates a contract. It's also wrong in v0.

}

// redeem redeems a Contract. It checks that the sender is not a contract,
// and that the secret hash hashes to secretHash. msg.value is tranfered
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not msg.value that is transferred, but the sum of the redemptions' values.

require(secretValidates(r.secret, r.c.secretHash), "invalid secret");

swaps[k] = r.secret;
amountToRedeem += r.c.value * 1 gwei;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not pass the value in wei instead of the value in gwei? Isn't this extra multiplication a waste of gas?

return 0
case 1:
return 1
case contractVersionNewest:
Copy link
Contributor

Choose a reason for hiding this comment

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

The contractVersionNewest case should actually return the newest version. And shouldn't the default case just return an error? How can an unknown version be used?

return false, time.Time{}, fmt.Errorf("error extracting contract locktime: %w", err)
}
contractExpiry := time.Unix(int64(locktime), 0).UTC()
func (btc *baseWallet) LocktimeExpired(_ context.Context, deets *dex.SwapContractDetails) (bool, time.Time, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not stick all the data from SwapContractDetails into the contract dex.Bytes?

@chappjc chappjc added this to the TBD milestone Sep 14, 2022
@chappjc chappjc added the ETH label Oct 12, 2022
buck54321 added a commit to buck54321/dcrdex that referenced this pull request Jan 10, 2023
Implements the version 1 contracts for ethereum and tokens. Based
on feedback in decred#1426, everything is now encoded in the
"contract data". This "contract data", is the msgjson.Init.Contract
-> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract,
AuditInfo.Contract -> Redemption.Spends.Contract.

A few new terms are introduced to differentiate various encodings and
data sets. The aforementioned contract data did encode a version
and a secret hash. It now encodes a version and a "locator", which is
a []byte whose length and content depend on the version. For
version 0, the locator is still just the secretHash[:]. For v1,
the locator encodes all of the immutable data that defines the
swap. This immutable data is now collected in something called
a "vector" (dexeth.SwapVector). For version 0, some vector data
is stored on-chain indexed by the secret hash. For version 1, all
vector data is encoded in the locator.

I've also made an effort to standardize the use of status/step,
and eliminated the use of ambiguous "ver" variables throughout.
A "status" is now the collection of mutable contract data: the step,
the init block height, and the secret. The status and vector
collectively fully characterize the swap.

client/asset/eth:
New contractV1 and tokenContractorV1 interfaces. To avoid duplication,
the ERC20 parts of the tokenContractors are separated into a new type
erc20Contractor that is embedded by both versions. Getters for
status and vector are added in place of the old method "swap".

assetWallet and embedding types are updated to work with the new
version-dependent locators and the status and vector model.

dex/networks/{eth,erc20}:
New contracts added. New methods for dealing with locators. Simnet
entries added for eth and dextt.eth in the ContractAddresses and Tokens
maps. txDataHandler interace is replaced with versioned package-level
functions.

server/asset/eth:
Server is fully switched to version 1. No option to use version 0.
Translation to new version was straightforward, with one notable
difference that we can no longer get a block height from the
contract once the swap is redeemed.
@buck54321 buck54321 mentioned this pull request Jan 10, 2023
@buck54321
Copy link
Member Author

Closing in favor of #2038

@buck54321 buck54321 closed this Jan 10, 2023
@chappjc chappjc removed this from the TBD milestone Jan 11, 2023
buck54321 added a commit to buck54321/dcrdex that referenced this pull request Jul 19, 2023
Implements the version 1 contracts for ethereum and tokens. Based
on feedback in decred#1426, everything is now encoded in the
"contract data". This "contract data", is the msgjson.Init.Contract
-> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract,
AuditInfo.Contract -> Redemption.Spends.Contract.

A few new terms are introduced to differentiate various encodings and
data sets. The aforementioned contract data did encode a version
and a secret hash. It now encodes a version and a "locator", which is
a []byte whose length and content depend on the version. For
version 0, the locator is still just the secretHash[:]. For v1,
the locator encodes all of the immutable data that defines the
swap. This immutable data is now collected in something called
a "vector" (dexeth.SwapVector). For version 0, some vector data
is stored on-chain indexed by the secret hash. For version 1, all
vector data is encoded in the locator.

I've also made an effort to standardize the use of status/step,
and eliminated the use of ambiguous "ver" variables throughout.
A "status" is now the collection of mutable contract data: the step,
the init block height, and the secret. The status and vector
collectively fully characterize the swap.

client/asset/eth:
New contractV1 and tokenContractorV1 interfaces. To avoid duplication,
the ERC20 parts of the tokenContractors are separated into a new type
erc20Contractor that is embedded by both versions. Getters for
status and vector are added in place of the old method "swap".

assetWallet and embedding types are updated to work with the new
version-dependent locators and the status and vector model.

dex/networks/{eth,erc20}:
New contracts added. New methods for dealing with locators. Simnet
entries added for eth and dextt.eth in the ContractAddresses and Tokens
maps. txDataHandler interace is replaced with versioned package-level
functions.

server/asset/eth:
Server is fully switched to version 1. No option to use version 0.
Translation to new version was straightforward, with one notable
difference that we can no longer get a block height from the
contract once the swap is redeemed.
buck54321 added a commit to buck54321/dcrdex that referenced this pull request Aug 7, 2023
Implements the version 1 contracts for ethereum and tokens. Based
on feedback in decred#1426, everything is now encoded in the
"contract data". This "contract data", is the msgjson.Init.Contract
-> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract,
AuditInfo.Contract -> Redemption.Spends.Contract.

A few new terms are introduced to differentiate various encodings and
data sets. The aforementioned contract data did encode a version
and a secret hash. It now encodes a version and a "locator", which is
a []byte whose length and content depend on the version. For
version 0, the locator is still just the secretHash[:]. For v1,
the locator encodes all of the immutable data that defines the
swap. This immutable data is now collected in something called
a "vector" (dexeth.SwapVector). For version 0, some vector data
is stored on-chain indexed by the secret hash. For version 1, all
vector data is encoded in the locator.

I've also made an effort to standardize the use of status/step,
and eliminated the use of ambiguous "ver" variables throughout.
A "status" is now the collection of mutable contract data: the step,
the init block height, and the secret. The status and vector
collectively fully characterize the swap.

client/asset/eth:
New contractV1 and tokenContractorV1 interfaces. To avoid duplication,
the ERC20 parts of the tokenContractors are separated into a new type
erc20Contractor that is embedded by both versions. Getters for
status and vector are added in place of the old method "swap".

assetWallet and embedding types are updated to work with the new
version-dependent locators and the status and vector model.

dex/networks/{eth,erc20}:
New contracts added. New methods for dealing with locators. Simnet
entries added for eth and dextt.eth in the ContractAddresses and Tokens
maps. txDataHandler interace is replaced with versioned package-level
functions.

server/asset/eth:
Server is fully switched to version 1. No option to use version 0.
Translation to new version was straightforward, with one notable
difference that we can no longer get a block height from the
contract once the swap is redeemed.
buck54321 added a commit to buck54321/dcrdex that referenced this pull request Sep 11, 2023
Implements the version 1 contracts for ethereum and tokens. Based
on feedback in decred#1426, everything is now encoded in the
"contract data". This "contract data", is the msgjson.Init.Contract
-> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract,
AuditInfo.Contract -> Redemption.Spends.Contract.

A few new terms are introduced to differentiate various encodings and
data sets. The aforementioned contract data did encode a version
and a secret hash. It now encodes a version and a "locator", which is
a []byte whose length and content depend on the version. For
version 0, the locator is still just the secretHash[:]. For v1,
the locator encodes all of the immutable data that defines the
swap. This immutable data is now collected in something called
a "vector" (dexeth.SwapVector). For version 0, some vector data
is stored on-chain indexed by the secret hash. For version 1, all
vector data is encoded in the locator.

I've also made an effort to standardize the use of status/step,
and eliminated the use of ambiguous "ver" variables throughout.
A "status" is now the collection of mutable contract data: the step,
the init block height, and the secret. The status and vector
collectively fully characterize the swap.

client/asset/eth:
New contractV1 and tokenContractorV1 interfaces. To avoid duplication,
the ERC20 parts of the tokenContractors are separated into a new type
erc20Contractor that is embedded by both versions. Getters for
status and vector are added in place of the old method "swap".

assetWallet and embedding types are updated to work with the new
version-dependent locators and the status and vector model.

dex/networks/{eth,erc20}:
New contracts added. New methods for dealing with locators. Simnet
entries added for eth and dextt.eth in the ContractAddresses and Tokens
maps. txDataHandler interace is replaced with versioned package-level
functions.

server/asset/eth:
Server is fully switched to version 1. No option to use version 0.
Translation to new version was straightforward, with one notable
difference that we can no longer get a block height from the
contract once the swap is redeemed.
buck54321 added a commit to buck54321/dcrdex that referenced this pull request Oct 27, 2023
Implements the version 1 contracts for ethereum and tokens. Based
on feedback in decred#1426, everything is now encoded in the
"contract data". This "contract data", is the msgjson.Init.Contract
-> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract,
AuditInfo.Contract -> Redemption.Spends.Contract.

A few new terms are introduced to differentiate various encodings and
data sets. The aforementioned contract data did encode a version
and a secret hash. It now encodes a version and a "locator", which is
a []byte whose length and content depend on the version. For
version 0, the locator is still just the secretHash[:]. For v1,
the locator encodes all of the immutable data that defines the
swap. This immutable data is now collected in something called
a "vector" (dexeth.SwapVector). For version 0, some vector data
is stored on-chain indexed by the secret hash. For version 1, all
vector data is encoded in the locator.

I've also made an effort to standardize the use of status/step,
and eliminated the use of ambiguous "ver" variables throughout.
A "status" is now the collection of mutable contract data: the step,
the init block height, and the secret. The status and vector
collectively fully characterize the swap.

client/asset/eth:
New contractV1 and tokenContractorV1 interfaces. To avoid duplication,
the ERC20 parts of the tokenContractors are separated into a new type
erc20Contractor that is embedded by both versions. Getters for
status and vector are added in place of the old method "swap".

assetWallet and embedding types are updated to work with the new
version-dependent locators and the status and vector model.

dex/networks/{eth,erc20}:
New contracts added. New methods for dealing with locators. Simnet
entries added for eth and dextt.eth in the ContractAddresses and Tokens
maps. txDataHandler interace is replaced with versioned package-level
functions.

server/asset/eth:
Server is fully switched to version 1. No option to use version 0.
Translation to new version was straightforward, with one notable
difference that we can no longer get a block height from the
contract once the swap is redeemed.
buck54321 added a commit to buck54321/dcrdex that referenced this pull request Mar 3, 2024
Implements the version 1 contracts for ethereum and tokens. Based
on feedback in decred#1426, everything is now encoded in the
"contract data". This "contract data", is the msgjson.Init.Contract
-> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract,
AuditInfo.Contract -> Redemption.Spends.Contract.

A few new terms are introduced to differentiate various encodings and
data sets. The aforementioned contract data did encode a version
and a secret hash. It now encodes a version and a "locator", which is
a []byte whose length and content depend on the version. For
version 0, the locator is still just the secretHash[:]. For v1,
the locator encodes all of the immutable data that defines the
swap. This immutable data is now collected in something called
a "vector" (dexeth.SwapVector). For version 0, some vector data
is stored on-chain indexed by the secret hash. For version 1, all
vector data is encoded in the locator.

I've also made an effort to standardize the use of status/step,
and eliminated the use of ambiguous "ver" variables throughout.
A "status" is now the collection of mutable contract data: the step,
the init block height, and the secret. The status and vector
collectively fully characterize the swap.

client/asset/eth:
New contractV1 and tokenContractorV1 interfaces. To avoid duplication,
the ERC20 parts of the tokenContractors are separated into a new type
erc20Contractor that is embedded by both versions. Getters for
status and vector are added in place of the old method "swap".

assetWallet and embedding types are updated to work with the new
version-dependent locators and the status and vector model.

dex/networks/{eth,erc20}:
New contracts added. New methods for dealing with locators. Simnet
entries added for eth and dextt.eth in the ContractAddresses and Tokens
maps. txDataHandler interace is replaced with versioned package-level
functions.

server/asset/eth:
Server is fully switched to version 1. No option to use version 0.
Translation to new version was straightforward, with one notable
difference that we can no longer get a block height from the
contract once the swap is redeemed.
buck54321 added a commit to buck54321/dcrdex that referenced this pull request Mar 22, 2024
Implements the version 1 contracts for ethereum and tokens. Based
on feedback in decred#1426, everything is now encoded in the
"contract data". This "contract data", is the msgjson.Init.Contract
-> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract,
AuditInfo.Contract -> Redemption.Spends.Contract.

A few new terms are introduced to differentiate various encodings and
data sets. The aforementioned contract data did encode a version
and a secret hash. It now encodes a version and a "locator", which is
a []byte whose length and content depend on the version. For
version 0, the locator is still just the secretHash[:]. For v1,
the locator encodes all of the immutable data that defines the
swap. This immutable data is now collected in something called
a "vector" (dexeth.SwapVector). For version 0, some vector data
is stored on-chain indexed by the secret hash. For version 1, all
vector data is encoded in the locator.

I've also made an effort to standardize the use of status/step,
and eliminated the use of ambiguous "ver" variables throughout.
A "status" is now the collection of mutable contract data: the step,
the init block height, and the secret. The status and vector
collectively fully characterize the swap.

client/asset/eth:
New contractV1 and tokenContractorV1 interfaces. To avoid duplication,
the ERC20 parts of the tokenContractors are separated into a new type
erc20Contractor that is embedded by both versions. Getters for
status and vector are added in place of the old method "swap".

assetWallet and embedding types are updated to work with the new
version-dependent locators and the status and vector model.

dex/networks/{eth,erc20}:
New contracts added. New methods for dealing with locators. Simnet
entries added for eth and dextt.eth in the ContractAddresses and Tokens
maps. txDataHandler interace is replaced with versioned package-level
functions.

server/asset/eth:
Server is fully switched to version 1. No option to use version 0.
Translation to new version was straightforward, with one notable
difference that we can no longer get a block height from the
contract once the swap is redeemed.
buck54321 added a commit to buck54321/dcrdex that referenced this pull request May 23, 2024
Implements the version 1 contracts for ethereum and tokens. Based
on feedback in decred#1426, everything is now encoded in the
"contract data". This "contract data", is the msgjson.Init.Contract
-> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract,
AuditInfo.Contract -> Redemption.Spends.Contract.

A few new terms are introduced to differentiate various encodings and
data sets. The aforementioned contract data did encode a version
and a secret hash. It now encodes a version and a "locator", which is
a []byte whose length and content depend on the version. For
version 0, the locator is still just the secretHash[:]. For v1,
the locator encodes all of the immutable data that defines the
swap. This immutable data is now collected in something called
a "vector" (dexeth.SwapVector). For version 0, some vector data
is stored on-chain indexed by the secret hash. For version 1, all
vector data is encoded in the locator.

I've also made an effort to standardize the use of status/step,
and eliminated the use of ambiguous "ver" variables throughout.
A "status" is now the collection of mutable contract data: the step,
the init block height, and the secret. The status and vector
collectively fully characterize the swap.

client/asset/eth:
New contractV1 and tokenContractorV1 interfaces. To avoid duplication,
the ERC20 parts of the tokenContractors are separated into a new type
erc20Contractor that is embedded by both versions. Getters for
status and vector are added in place of the old method "swap".

assetWallet and embedding types are updated to work with the new
version-dependent locators and the status and vector model.

dex/networks/{eth,erc20}:
New contracts added. New methods for dealing with locators. Simnet
entries added for eth and dextt.eth in the ContractAddresses and Tokens
maps. txDataHandler interace is replaced with versioned package-level
functions.

server/asset/eth:
Server is fully switched to version 1. No option to use version 0.
Translation to new version was straightforward, with one notable
difference that we can no longer get a block height from the
contract once the swap is redeemed.
buck54321 added a commit to buck54321/dcrdex that referenced this pull request Jun 3, 2024
Implements the version 1 contracts for ethereum and tokens. Based
on feedback in decred#1426, everything is now encoded in the
"contract data". This "contract data", is the msgjson.Init.Contract
-> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract,
AuditInfo.Contract -> Redemption.Spends.Contract.

A few new terms are introduced to differentiate various encodings and
data sets. The aforementioned contract data did encode a version
and a secret hash. It now encodes a version and a "locator", which is
a []byte whose length and content depend on the version. For
version 0, the locator is still just the secretHash[:]. For v1,
the locator encodes all of the immutable data that defines the
swap. This immutable data is now collected in something called
a "vector" (dexeth.SwapVector). For version 0, some vector data
is stored on-chain indexed by the secret hash. For version 1, all
vector data is encoded in the locator.

I've also made an effort to standardize the use of status/step,
and eliminated the use of ambiguous "ver" variables throughout.
A "status" is now the collection of mutable contract data: the step,
the init block height, and the secret. The status and vector
collectively fully characterize the swap.

client/asset/eth:
New contractV1 and tokenContractorV1 interfaces. To avoid duplication,
the ERC20 parts of the tokenContractors are separated into a new type
erc20Contractor that is embedded by both versions. Getters for
status and vector are added in place of the old method "swap".

assetWallet and embedding types are updated to work with the new
version-dependent locators and the status and vector model.

dex/networks/{eth,erc20}:
New contracts added. New methods for dealing with locators. Simnet
entries added for eth and dextt.eth in the ContractAddresses and Tokens
maps. txDataHandler interace is replaced with versioned package-level
functions.

server/asset/eth:
Server is fully switched to version 1. No option to use version 0.
Translation to new version was straightforward, with one notable
difference that we can no longer get a block height from the
contract once the swap is redeemed.
buck54321 added a commit to buck54321/dcrdex that referenced this pull request Jul 4, 2024
Implements the version 1 contracts for ethereum and tokens. Based
on feedback in decred#1426, everything is now encoded in the
"contract data". This "contract data", is the msgjson.Init.Contract
-> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract,
AuditInfo.Contract -> Redemption.Spends.Contract.

A few new terms are introduced to differentiate various encodings and
data sets. The aforementioned contract data did encode a version
and a secret hash. It now encodes a version and a "locator", which is
a []byte whose length and content depend on the version. For
version 0, the locator is still just the secretHash[:]. For v1,
the locator encodes all of the immutable data that defines the
swap. This immutable data is now collected in something called
a "vector" (dexeth.SwapVector). For version 0, some vector data
is stored on-chain indexed by the secret hash. For version 1, all
vector data is encoded in the locator.

I've also made an effort to standardize the use of status/step,
and eliminated the use of ambiguous "ver" variables throughout.
A "status" is now the collection of mutable contract data: the step,
the init block height, and the secret. The status and vector
collectively fully characterize the swap.

client/asset/eth:
New contractV1 and tokenContractorV1 interfaces. To avoid duplication,
the ERC20 parts of the tokenContractors are separated into a new type
erc20Contractor that is embedded by both versions. Getters for
status and vector are added in place of the old method "swap".

assetWallet and embedding types are updated to work with the new
version-dependent locators and the status and vector model.

dex/networks/{eth,erc20}:
New contracts added. New methods for dealing with locators. Simnet
entries added for eth and dextt.eth in the ContractAddresses and Tokens
maps. txDataHandler interace is replaced with versioned package-level
functions.

server/asset/eth:
Server is fully switched to version 1. No option to use version 0.
Translation to new version was straightforward, with one notable
difference that we can no longer get a block height from the
contract once the swap is redeemed.
buck54321 added a commit to buck54321/dcrdex that referenced this pull request Nov 13, 2024
Implements the version 1 contracts for ethereum and tokens. Based
on feedback in decred#1426, everything is now encoded in the
"contract data". This "contract data", is the msgjson.Init.Contract
-> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract,
AuditInfo.Contract -> Redemption.Spends.Contract.

A few new terms are introduced to differentiate various encodings and
data sets. The aforementioned contract data did encode a version
and a secret hash. It now encodes a version and a "locator", which is
a []byte whose length and content depend on the version. For
version 0, the locator is still just the secretHash[:]. For v1,
the locator encodes all of the immutable data that defines the
swap. This immutable data is now collected in something called
a "vector" (dexeth.SwapVector). For version 0, some vector data
is stored on-chain indexed by the secret hash. For version 1, all
vector data is encoded in the locator.

I've also made an effort to standardize the use of status/step,
and eliminated the use of ambiguous "ver" variables throughout.
A "status" is now the collection of mutable contract data: the step,
the init block height, and the secret. The status and vector
collectively fully characterize the swap.

client/asset/eth:
New contractV1 and tokenContractorV1 interfaces. To avoid duplication,
the ERC20 parts of the tokenContractors are separated into a new type
erc20Contractor that is embedded by both versions. Getters for
status and vector are added in place of the old method "swap".

assetWallet and embedding types are updated to work with the new
version-dependent locators and the status and vector model.

dex/networks/{eth,erc20}:
New contracts added. New methods for dealing with locators. Simnet
entries added for eth and dextt.eth in the ContractAddresses and Tokens
maps. txDataHandler interace is replaced with versioned package-level
functions.

server/asset/eth:
Server is fully switched to version 1. No option to use version 0.
Translation to new version was straightforward, with one notable
difference that we can no longer get a block height from the
contract once the swap is redeemed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants