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

Handle withdrawal tx fee #108

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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 @@

"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 @@
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 @@
_ = 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 All @@ -95,7 +99,7 @@

// NewBtcSigningRequest creates a signing request for runes withdrawal
func (k Keeper) NewRunesSigningRequest(ctx sdk.Context, sender string, coin sdk.Coin, feeRate int64, vault string, btcVault string) (*types.BitcoinSigningRequest, error) {
var runeId types.RuneId

Check warning on line 102 in x/btcbridge/keeper/keeper_withdraw.go

View workflow job for this annotation

GitHub Actions / golangci-lint

var-naming: var runeId should be runeID (revive)
runeId.FromDenom(coin.Denom)

amount := uint128.FromBig(coin.Amount.BigInt())
Expand All @@ -115,6 +119,10 @@
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 @@
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 @@
}
}
}

// 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
Loading