Skip to content

Commit

Permalink
Merge pull request #108 from keithsue/sufay/withdrawal-fee-handling
Browse files Browse the repository at this point in the history
Handle withdrawal tx fee
  • Loading branch information
keithsue authored Jul 10, 2024
2 parents 1671fc4 + f9ffed9 commit 252b2eb
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 6 deletions.
111 changes: 106 additions & 5 deletions x/btcbridge/keeper/keeper_withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"

Expand Down Expand Up @@ -60,7 +61,12 @@ func (k Keeper) NewBtcSigningRequest(ctx sdk.Context, sender string, coin sdk.Co
return nil, types.ErrInsufficientUTXOs
}

psbt, selectedUTXOs, changeUTXO, err := types.BuildPsbt(utxos, sender, coin.Amount.Int64(), feeRate, vault)
psbt, selectedUTXOs, _, err := types.BuildPsbt(utxos, sender, coin.Amount.Int64(), feeRate, vault)
if err != nil {
return nil, err
}

changeUTXO, err := k.handleBtcTxFee(psbt, vault)
if err != nil {
return nil, err
}
Expand All @@ -74,10 +80,8 @@ func (k Keeper) NewBtcSigningRequest(ctx sdk.Context, sender string, coin sdk.Co
_ = k.LockUTXOs(ctx, selectedUTXOs)

// save the change utxo and mark minted
if changeUTXO != nil {
k.saveUTXO(ctx, changeUTXO)
k.addToMintHistory(ctx, psbt.UnsignedTx.TxHash().String())
}
k.saveUTXO(ctx, changeUTXO)
k.addToMintHistory(ctx, psbt.UnsignedTx.TxHash().String())

signingRequest := &types.BitcoinSigningRequest{
Address: sender,
Expand Down Expand Up @@ -115,6 +119,10 @@ func (k Keeper) NewRunesSigningRequest(ctx sdk.Context, sender string, coin sdk.
return nil, err
}

if err := k.handleRunesTxFee(ctx, psbt, sender); err != nil {
return nil, err
}

psbtB64, err := psbt.B64Encode()
if err != nil {
return nil, types.ErrFailToSerializePsbt
Expand Down Expand Up @@ -284,8 +292,14 @@ func (k Keeper) ProcessBitcoinWithdrawTransaction(ctx sdk.Context, msg *types.Ms
return nil, err
}

// spend the locked utxos
k.spendUTXOs(ctx, uTx)

// burn the locked asset
if err := k.burnLockedAsset(ctx, txHash.String()); err != nil {
return nil, err
}

return &txHash, nil
}

Expand All @@ -300,3 +314,90 @@ func (k Keeper) spendUTXOs(ctx sdk.Context, uTx *btcutil.Tx) {
}
}
}

// handleTxFee performs the fee handling for the btc withdrawal tx
// Make sure that the given psbt is valid
// There are at most two outputs and the change output is the last one if any
func (k Keeper) handleBtcTxFee(p *psbt.Packet, changeAddr string) (*types.UTXO, error) {
recipientOut := p.UnsignedTx.TxOut[0]

changeOut := new(wire.TxOut)
if len(p.UnsignedTx.TxOut) > 1 {
changeOut = p.UnsignedTx.TxOut[1]
} else {
changeOut = wire.NewTxOut(0, types.MustPkScriptFromAddress(changeAddr))
p.UnsignedTx.TxOut = append(p.UnsignedTx.TxOut, changeOut)
}

txFee, err := p.GetTxFee()
if err != nil {
return nil, err
}

recipientOut.Value -= int64(txFee)
changeOut.Value += int64(txFee)

if types.IsDustOut(recipientOut) || types.IsDustOut(changeOut) {
return nil, types.ErrDustOutput
}

return &types.UTXO{
Txid: p.UnsignedTx.TxHash().String(),
Vout: 1,
Address: changeAddr,
Amount: uint64(changeOut.Value),
PubKeyScript: changeOut.PkScript,
}, nil
}

// handleRunesTxFee performs the fee handling for the runes withdrawal tx
func (k Keeper) handleRunesTxFee(ctx sdk.Context, p *psbt.Packet, recipient string) error {
txFee, err := p.GetTxFee()
if err != nil {
return err
}

feeCoin := sdk.NewCoin(k.GetParams(ctx).BtcVoucherDenom, sdk.NewInt(int64(txFee)))
if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sdk.MustAccAddressFromBech32(recipient), types.ModuleName, sdk.NewCoins(feeCoin)); err != nil {
return err
}

k.lockAsset(ctx, p.UnsignedTx.TxHash().String(), feeCoin)

return nil
}

// lockAsset locks the given asset by the tx hash
func (k Keeper) lockAsset(ctx sdk.Context, txHash string, coin sdk.Coin) {
store := ctx.KVStore(k.storeKey)

bz := k.cdc.MustMarshal(&coin)
store.Set(types.BtcLockedAssetKey(txHash), bz)
}

// getLockedAsset gets the locked asset by the tx hash
func (k Keeper) getLockedAsset(ctx sdk.Context, txHash string) sdk.Coin {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.BtcLockedAssetKey(txHash))

var coin sdk.Coin
k.cdc.MustUnmarshal(bz, &coin)

return coin
}

// burnLockedAsset burns the locked asset
func (k Keeper) burnLockedAsset(ctx sdk.Context, txHash string) error {
store := ctx.KVStore(k.storeKey)

if store.Has(types.BtcLockedAssetKey(txHash)) {
lockedCoin := k.getLockedAsset(ctx, txHash)
if err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(lockedCoin)); err != nil {
return err
}

store.Delete(types.BtcLockedAssetKey(txHash))
}

return nil
}
16 changes: 16 additions & 0 deletions x/btcbridge/types/bitcoin_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,19 @@ func CheckOutputAmount(address string, amount int64) error {
func IsOpReturnOutput(out *wire.TxOut) bool {
return len(out.PkScript) > 0 && out.PkScript[0] == txscript.OP_RETURN
}

// MustPkScriptFromAddress returns the public script of the given address
// Panic if any error occurred
func MustPkScriptFromAddress(address string) []byte {
addr, err := btcutil.DecodeAddress(address, sdk.GetConfig().GetBtcChainCfg())
if err != nil {
panic(err)
}

pkScript, err := txscript.PayToAddrScript(addr)
if err != nil {
panic(err)
}

return pkScript
}
6 changes: 6 additions & 0 deletions x/btcbridge/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ var (
BtcOwnerRunesUtxoKeyPrefix = []byte{0x17} // prefix for each key to an owned runes utxo

BtcMintedTxHashKeyPrefix = []byte{0x18} // prefix for each key to a minted tx hash

BtcLockedAssetKeyPrefix = []byte{0x19} // prefix for each key to the locked asset
)

func Int64ToBytes(number uint64) []byte {
Expand Down Expand Up @@ -84,3 +86,7 @@ func BtcSigningRequestHashKey(txid string) []byte {
func BtcMintedTxHashKey(hash string) []byte {
return append(BtcMintedTxHashKeyPrefix, []byte(hash)...)
}

func BtcLockedAssetKey(txHash string) []byte {
return append(BtcLockedAssetKeyPrefix, []byte(txHash)...)
}
2 changes: 1 addition & 1 deletion x/btcbridge/types/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (p Params) Validate() error {
}

if vault.AssetType == AssetType_ASSET_TYPE_UNSPECIFIED {
return err
return ErrInvalidParams
}
}

Expand Down

0 comments on commit 252b2eb

Please sign in to comment.