Skip to content

Commit

Permalink
Merge pull request #265 from sCrypt-Inc/logical-utxo
Browse files Browse the repository at this point in the history
Logical utxo
  • Loading branch information
msinkec authored Aug 14, 2023
2 parents ea2754c + 0ea3705 commit e34998f
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 74 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@ artifacts/
.env
scrypt.index.json
.eslintcache
tsconfig-scryptTS.json
30 changes: 15 additions & 15 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"author": "",
"license": "MIT",
"dependencies": {
"scrypt-ts": "^1.3.0",
"scrypt-ts": "^1.3.1",
"scrypt-ts-lib": "^0.1.20"
},
"devDependencies": {
Expand Down
112 changes: 112 additions & 0 deletions src/contracts/virtualUTXO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {
assert,
hash256,
int2ByteString,
method,
Sig,
SigHash,
slice,
SmartContract,
len,
byteString2Int,
prop,
UTXO,
bsv,
} from 'scrypt-ts'

export class VirtualUTXO extends SmartContract {
static readonly LOGICAL_UTXO_SIZE = 3

@prop(true)
dummyStateProp: bigint

constructor() {
super(...arguments)
this.dummyStateProp = 0n
}

@method(SigHash.SINGLE)
public unlock() {
// Get prevouts array and verify it.
const prevouts = this.prevouts
assert(
hash256(prevouts) == this.ctx.hashPrevouts,
'hashPrevouts mismatch'
)

// The prevout of the current input which is being executed.
const currentPrevout =
this.ctx.utxo.outpoint.txid +
int2ByteString(this.ctx.utxo.outpoint.outputIndex, 4n)

for (let i = 0; i < VirtualUTXO.LOGICAL_UTXO_SIZE; i++) {
const start = BigInt(i) * 36n
const end = start + 36n
const prevout = slice(prevouts, start, end)

// Within the prevouts array, find the prevout for the currently executed input.
if (prevout == currentPrevout) {
const currentPrevoutTXID = slice(currentPrevout, 0n, 32n)
const currentPrevoutOutputIndex = byteString2Int(
slice(currentPrevout, 32n, 36n)
)

const isLastInput = i == VirtualUTXO.LOGICAL_UTXO_SIZE - 1

// Check the subsequent input is unlocking the subsequent output index from the same transaction.
// If the last input is being executed, the subsequent input is the FIRST one. This ensures a circular
// verification structure.
const startNext = isLastInput ? 0n : end
const nextPrevout = slice(prevouts, startNext, startNext + 36n)
const nextPrevoutTXID = slice(nextPrevout, 0n, 32n)
const nextPrevoutOutputIndex = byteString2Int(
slice(nextPrevout, 32n, 36n)
)
assert(
currentPrevoutTXID == nextPrevoutTXID,
'next input TXID mismatch'
)
assert(
nextPrevoutOutputIndex ==
(isLastInput ? 0n : currentPrevoutOutputIndex + 1n),
'next input not unlocking subsequent output idx'
)
}
}

// Propagate contract.
const outputs = this.buildStateOutput(this.ctx.utxo.value)
assert(hash256(outputs) == this.ctx.hashOutputs, 'hashOutputs mismatch')
}

// Customize the deployment TX to include multiple instances of this smart contract,
// equal to the specified size of our logical UTXO.
override async buildDeployTransaction(
utxos: UTXO[],
amount: number,
changeAddress?: bsv.Address | string
): Promise<bsv.Transaction> {
const deployTx = new bsv.Transaction()
// Add p2pkh inputs for paying tx fees.
.from(utxos)

// Add logical UTXO outputs.
for (let i = 0; i < VirtualUTXO.LOGICAL_UTXO_SIZE; i++) {
deployTx.addOutput(
new bsv.Transaction.Output({
script: this.lockingScript,
satoshis: amount,
})
)
}

if (changeAddress) {
deployTx.change(changeAddress)
if (this._provider) {
deployTx.feePerKb(await this.provider.getFeePerKb())
}
}

return deployTx
}
}
45 changes: 2 additions & 43 deletions tests/local/ordinalAuction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
MethodCallOptions,
PubKey,
reverseByteString,
Sig,
toByteString,
toHex,
Utils,
Expand Down Expand Up @@ -101,9 +100,7 @@ describe('Test SmartContract `OrdinalAuction`', () => {
'close',
async (
current: OrdinalAuction,
options: MethodCallOptions<OrdinalAuction>,
sigAuctioneer: Sig,
prevouts: ByteString
options: MethodCallOptions<OrdinalAuction>
) => {
const unsignedTx: bsv.Transaction = new bsv.Transaction()

Expand Down Expand Up @@ -164,45 +161,7 @@ describe('Test SmartContract `OrdinalAuction`', () => {
}
)

let contractTx = await currentInstance.methods.close(
(sigResps) => findSig(sigResps, publicKeyAuctioneer),
{
fromUTXO,
pubKeyOrAddrToSign: publicKeyAuctioneer,
changeAddress: addressAuctioneer,
lockTime: auctionDeadline + 1,
sequence: 0,
exec: false, // Do not execute the contract yet, only get the created calling transaction.
} as MethodCallOptions<OrdinalAuction>
)

currentInstance.bindTxBuilder(
'close',
async (
current: OrdinalAuction,
options: MethodCallOptions<OrdinalAuction>,
sigAuctioneer: Sig,
prevouts: ByteString
) => {
return Promise.resolve({
tx: contractTx.tx,
atInputIndex: 1,
nexts: [],
})
}
)

// Assemble prevouts byte string.
let prevouts = toByteString('')
contractTx.tx.inputs.forEach((input) => {
prevouts += reverseByteString(
toByteString(input.prevTxId.toString('hex')),
32n
)
prevouts += int2ByteString(BigInt(input.outputIndex), 4n)
})

contractTx = await currentInstance.methods.close(
const contractTx = await currentInstance.methods.close(
(sigResps) => findSig(sigResps, publicKeyAuctioneer),
{
fromUTXO,
Expand Down
Loading

0 comments on commit e34998f

Please sign in to comment.