Skip to content

Commit

Permalink
Merge pull request #149 from libsv/feat/bid-list-1-dummy
Browse files Browse the repository at this point in the history
  • Loading branch information
jadwahab authored Apr 9, 2023
2 parents da68c79 + 07f43a3 commit 79b6991
Show file tree
Hide file tree
Showing 10 changed files with 575 additions and 111 deletions.
65 changes: 12 additions & 53 deletions bscript/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,55 +325,8 @@ func (s *Script) IsData() bool {
// IsInscribed returns true if this script includes an
// inscription with any prepended script (not just p2pkh).
func (s *Script) IsInscribed() bool {
/* TODO: write full code
code generated by ChatGPT to use as a start:
// bytesContainsTemplate searches for a template sequence of bytes in the byte array.
// The template sequence is a slice of byte slices, where each byte slice represents a sequence of bytes to search for.
// An empty byte slice represents any sequence of bytes.
// The function returns true if the template sequence is found in the byte array, and false otherwise.
func bytesContainsTemplate(byteArray []byte, templateSequence [][]byte) bool {
if len(templateSequence) == 0 {
return true
}
currentIndex := 0
for _, searchSequence := range templateSequence {
index := bytesIndex(byteArray[currentIndex:], searchSequence)
if index == -1 {
return false
}
currentIndex += index + len(searchSequence)
}
return true
}
// bytesIndex returns the index of the first occurrence of the search sequence in the byte array,
// or -1 if the search sequence is not found
func bytesIndex(byteArray []byte, searchSequence []byte) int {
if len(searchSequence) == 0 {
return 0
}
for i := 0; i < len(byteArray); i++ {
if byteArray[i] == searchSequence[0] && i+len(searchSequence) <= len(byteArray) {
match := true
for j := 1; j < len(searchSequence); j++ {
if searchSequence[j] != 0 && byteArray[i+j] != searchSequence[j] {
match = false
break
}
}
if match {
return i
}
}
}
return -1
}
*/
return false
isncPattern, _ := hex.DecodeString("0063036f7264")
return bytes.Contains(*s, isncPattern)
}

// IsP2PKHInscription checks if it's a standard
Expand All @@ -390,9 +343,10 @@ func (s *Script) IsP2PKHInscription() bool {
// isP2PKHInscriptionHelper helper so that we don't need to call
// `DecodeParts()` multiple times, such as in `ParseInscription()`
func isP2PKHInscriptionHelper(parts [][]byte) bool {
// TODO: cleanup
return len(parts) == 13 &&
parts[0][0] == OpDUP &&
if len(parts) < 13 {
return false
}
valid := parts[0][0] == OpDUP &&
parts[1][0] == OpHASH160 &&
parts[3][0] == OpEQUALVERIFY &&
parts[4][0] == OpCHECKSIG &&
Expand All @@ -402,11 +356,16 @@ func isP2PKHInscriptionHelper(parts [][]byte) bool {
parts[8][0] == OpTRUE &&
parts[10][0] == OpFALSE &&
parts[12][0] == OpENDIF

if len(parts) > 13 {
return parts[13][0] == OpRETURN && valid
}
return valid
}

// ParseInscription parses the script to
// return the inscription found. Will return
// an error if the scription doesn't contain
// an error if the script doesn't contain
// any inscriptions.
func (s *Script) ParseInscription() (*InscriptionArgs, error) {
p, err := DecodeParts(*s)
Expand Down
27 changes: 27 additions & 0 deletions bscript/script_test.go

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ var (

// Sentinal errors reported by PSBTs.
var (
ErrDummyInput = errors.New("failed to add dummy input 0")
ErrInsufficientUTXOs = errors.New("need at least 3 utxos")
ErrUTXOInputMismatch = errors.New("utxo and input mismatch")
ErrInvalidSellOffer = errors.New("invalid sell offer (partially signed tx)")
ErrOrdinalOutputNoExist = errors.New("ordinal output expected in index 2 doesn't exist")
ErrOrdinalInputNoExist = errors.New("ordinal input expected in index 2 doesn't exist")
ErrDummyInput = errors.New("failed to add dummy input 0")
ErrInsufficientUTXOs = errors.New("need at least 2 utxos")
ErrInsufficientUTXOValue = errors.New("need at least 1 utxos which is > ordinal price")
ErrUTXOInputMismatch = errors.New("utxo and input mismatch")
ErrInvalidSellOffer = errors.New("invalid sell offer (partially signed tx)")
ErrEmptyScripts = errors.New("at least one of needed scripts is empty")
ErrInsufficientFees = errors.New("fee paid not enough with new locking script")
)
112 changes: 98 additions & 14 deletions ord/bidding.go → ord/2dummies.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,91 @@ import (
"github.com/pkg/errors"
)

// MakeBidArgs contains the arguments
// TODO: are 2 dummies useful or to be removed?

// AcceptOrdinalSaleListing2Dummies accepts a partially signed Bitcoin
// transaction offer to sell an ordinal. When accepting the offer,
// you will need to provide at least 3 UTXOs - with the first 2
// being dummy utxos that will just pass through, and the rest with
// the required payment and tx fees.
func AcceptOrdinalSaleListing2Dummies(ctx context.Context, vla *ValidateListingArgs,
asoa *AcceptListingArgs) (*bt.Tx, error) {

if valid := vla.Validate(asoa.PSTx); !valid {
return nil, bt.ErrInvalidSellOffer
}
sellerOrdinalInput := asoa.PSTx.Inputs[0]
sellerOutput := asoa.PSTx.Outputs[0]

if len(asoa.UTXOs) < 3 {
return nil, bt.ErrInsufficientUTXOs
}

tx := bt.NewTx()

// add dummy inputs
err := tx.FromUTXOs(asoa.UTXOs[0], asoa.UTXOs[1])
if err != nil {
return nil, fmt.Errorf(`failed to add inputs: %w`, err)
}

tx.Inputs = append(tx.Inputs, sellerOrdinalInput)

// add payment input(s)
err = tx.FromUTXOs(asoa.UTXOs[2:]...)
if err != nil {
return nil, fmt.Errorf(`failed to add inputs: %w`, err)
}

// add dummy output to passthrough dummy inputs
tx.AddOutput(&bt.Output{
LockingScript: asoa.DummyOutputScript,
Satoshis: asoa.UTXOs[0].Satoshis + asoa.UTXOs[1].Satoshis,
})

// add ordinal receive output
tx.AddOutput(&bt.Output{
LockingScript: asoa.BuyerReceiveOrdinalScript,
Satoshis: 1,
})

tx.AddOutput(sellerOutput)

err = tx.Change(asoa.ChangeScript, asoa.FQ)
if err != nil {
return nil, err
}

//nolint:dupl // TODO: are 2 dummies useful or to be removed?
for i, u := range asoa.UTXOs {
// skip 3rd input (ordinals input)
j := i
if i >= 2 {
j++
}

if tx.Inputs[j] == nil {
return nil, fmt.Errorf("input expected at index %d doesn't exist", j)
}
if !(bytes.Equal(u.TxID, tx.Inputs[j].PreviousTxID())) {
return nil, bt.ErrUTXOInputMismatch
}
if *u.Unlocker == nil {
return nil, fmt.Errorf("UTXO unlocker at index %d not found", i)
}
err = tx.FillInput(ctx, *u.Unlocker, bt.UnlockerParams{InputIdx: uint32(j)})
if err != nil {
return nil, err
}
}

return tx, nil
}

// MakeBid2DArgs contains the arguments
// needed to make a bid to buy an
// ordinal.
type MakeBidArgs struct {
type MakeBid2DArgs struct {
BidAmount uint64
OrdinalTxID string
OrdinalVOut uint32
Expand All @@ -34,7 +115,7 @@ type MakeBidArgs struct {
//
// Note: this function is meant for ordinals in 1 satoshi outputs instead
// of ordinal ranges in 1 output (>1 satoshi outputs).
func MakeBidToBuy1SatOrdinal(ctx context.Context, mba *MakeBidArgs) (*bt.Tx, error) {
func MakeBidToBuy1SatOrdinal2Dummies(ctx context.Context, mba *MakeBid2DArgs) (*bt.Tx, error) {
if len(mba.BidderUTXOs) < 3 {
return nil, bt.ErrInsufficientUTXOs
}
Expand Down Expand Up @@ -98,6 +179,7 @@ func MakeBidToBuy1SatOrdinal(ctx context.Context, mba *MakeBidArgs) (*bt.Tx, err
return nil, err
}

//nolint: dupl // TODO: are 2 dummies useful or to be removed?
for i, u := range mba.BidderUTXOs {
// skip 3rd input (ordinals input)
j := i
Expand Down Expand Up @@ -126,19 +208,19 @@ func MakeBidToBuy1SatOrdinal(ctx context.Context, mba *MakeBidArgs) (*bt.Tx, err
return tx, nil
}

// ValidateBidArgs are the arguments needed to
// ValidateBid2DArgs are the arguments needed to
// validate a specific bid to buy an ordinal.
//
// Note: index 2 should be the listed ordinal input.
type ValidateBidArgs struct {
type ValidateBid2DArgs struct {
PreviousUTXOs []*bt.UTXO // index 2 should be the listed ordinal input
BidAmount uint64
ExpectedFQ *bt.FeeQuote
}

// Validate a bid to buy an ordinal
// given specific validation parameters.
func (vba *ValidateBidArgs) Validate(pstx *bt.Tx) bool {
func (vba *ValidateBid2DArgs) Validate(pstx *bt.Tx) bool {
if pstx.InputCount() < 4 {
return false
}
Expand Down Expand Up @@ -174,7 +256,7 @@ func (vba *ValidateBidArgs) Validate(pstx *bt.Tx) bool {
return false
}

// check enough funds paid
// check enough fees paid
pstx.Outputs[2].Satoshis = vba.BidAmount
enough, err := pstx.IsFeePaidEnough(vba.ExpectedFQ)
if err != nil || !enough {
Expand All @@ -186,20 +268,22 @@ func (vba *ValidateBidArgs) Validate(pstx *bt.Tx) bool {
return true
}

// AcceptBidArgs contains the arguments
// needed to make an offer to sell an
// AcceptBid2DArgs contains the arguments
// needed to accept a bid to buy an
// ordinal.
type AcceptBidArgs struct {
type AcceptBid2DArgs struct {
PSTx *bt.Tx
SellerReceiveOrdinalScript *bscript.Script
OrdinalUnlocker bt.Unlocker
ExtraUTXOs []*bt.UTXO
}

// AcceptBidToBuy1SatOrdinal creates a PBST (Partially Signed Bitcoin
// AcceptBidToBuy1SatOrdinal2Dummies creates a PBST (Partially Signed Bitcoin
// Transaction) that offers a specific ordinal UTXO for sale at a
// specific price.
func AcceptBidToBuy1SatOrdinal(ctx context.Context, vba *ValidateBidArgs, aba *AcceptBidArgs) (*bt.Tx, error) {
func AcceptBidToBuy1SatOrdinal2Dummies(ctx context.Context, vba *ValidateBid2DArgs,
aba *AcceptBid2DArgs) (*bt.Tx, error) {

if valid := vba.Validate(aba.PSTx); !valid {
return nil, bt.ErrInvalidSellOffer
}
Expand All @@ -219,12 +303,12 @@ func AcceptBidToBuy1SatOrdinal(ctx context.Context, vba *ValidateBidArgs, aba *A
}

if tx.Outputs[2] == nil {
return nil, bt.ErrOrdinalOutputNoExist
return nil, errors.New("ordinal output expected in index 2 doesn't exist")
}
tx.Outputs[2].LockingScript = aba.SellerReceiveOrdinalScript

if tx.Inputs[2] == nil {
return nil, bt.ErrOrdinalInputNoExist
return nil, errors.New("ordinal input expected in index 2 doesn't exist")
}
tx.Inputs[2].PreviousTxScript = vba.PreviousUTXOs[2].LockingScript
tx.Inputs[2].PreviousTxSatoshis = vba.PreviousUTXOs[2].Satoshis
Expand Down
10 changes: 5 additions & 5 deletions ord/bidding_test.go → ord/2dummies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/stretchr/testify/assert"
)

func TestBidToBuyPSBTNoErrors(t *testing.T) {
func TestBidToBuyPSBT2DNoErrors(t *testing.T) {
fundingWif, _ := wif.DecodeWIF("L5W2nyKUCsDStVUBwZj2Q3Ph5vcae4bgdzprZDYqDpvZA8AFguFH") // 19NfKd8aTwvb5ngfP29RxgfQzZt8KAYtQo
fundingAddr, _ := bscript.NewAddressFromPublicKeyString(hex.EncodeToString(fundingWif.SerialisePubKey()), true)
fundingScript, _ := bscript.NewP2PKHFromAddress(fundingAddr.AddressString)
Expand Down Expand Up @@ -74,7 +74,7 @@ func TestBidToBuyPSBTNoErrors(t *testing.T) {
Satoshis: 1,
}

pstx, CreateBidError := ord.MakeBidToBuy1SatOrdinal(context.Background(), &ord.MakeBidArgs{
pstx, CreateBidError := ord.MakeBidToBuy1SatOrdinal2Dummies(context.Background(), &ord.MakeBid2DArgs{
BidAmount: uint64(bidAmount),
OrdinalTxID: ordUTXO.TxIDStr(),
OrdinalVOut: ordUTXO.Vout,
Expand All @@ -99,7 +99,7 @@ func TestBidToBuyPSBTNoErrors(t *testing.T) {
})

t.Run("validate PSBT bid to buy ordinal", func(t *testing.T) {
vba := &ord.ValidateBidArgs{
vba := &ord.ValidateBid2DArgs{
BidAmount: uint64(bidAmount),
ExpectedFQ: bt.NewFeeQuote(),
// insert ordinal utxo at index 2
Expand All @@ -109,12 +109,12 @@ func TestBidToBuyPSBTNoErrors(t *testing.T) {
})

t.Run("no errors when accepting bid", func(t *testing.T) {
_, err := ord.AcceptBidToBuy1SatOrdinal(context.Background(), &ord.ValidateBidArgs{
_, err := ord.AcceptBidToBuy1SatOrdinal2Dummies(context.Background(), &ord.ValidateBid2DArgs{
BidAmount: uint64(bidAmount),
ExpectedFQ: bt.NewFeeQuote(),
PreviousUTXOs: append(us[:2], append([]*bt.UTXO{ordUTXO}, us[2:]...)...),
},
&ord.AcceptBidArgs{
&ord.AcceptBid2DArgs{
PSTx: pstx,
SellerReceiveOrdinalScript: func() *bscript.Script {
s, _ := bscript.NewP2PKHFromAddress("1C3V9TTJefP8Hft96sVf54mQyDJh8Ze4w4") // L1JWiLZtCkkqin41XtQ2Jxo1XGxj1R4ydT2zmxPiaeQfuyUK631D
Expand Down
Loading

0 comments on commit 79b6991

Please sign in to comment.