Skip to content

Commit 7828d11

Browse files
committed
Refactor last remaining TODO in LibauthTemplate
1 parent 0072f76 commit 7828d11

2 files changed

Lines changed: 63 additions & 82 deletions

File tree

packages/cashscript/src/libauth-template/LibauthTemplate.ts

Lines changed: 62 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
isP2PKHUnlocker,
2626
isStandardUnlockableUtxo,
2727
isUnlockableUtxo,
28+
LibauthOutput,
2829
Output,
2930
StandardUnlockableUtxo,
3031
Utxo,
@@ -34,7 +35,7 @@ import { addressToLockScript, extendedStringify, zip } from '../utils.js';
3435
import { TransactionBuilder } from '../TransactionBuilder.js';
3536
import { deflate } from 'pako';
3637
import MockNetworkProvider from '../network/MockNetworkProvider.js';
37-
import { addHexPrefixExceptEmpty, DEFAULT_VM_TARGET, formatBytecodeForDebugging, formatParametersForDebugging, getLockScriptName, getSignatureAndPubkeyFromP2PKHInput, getUnlockScriptName, lockingBytecodeIsSetToSlot, serialiseTokenDetails } from './utils.js';
38+
import { addHexPrefixExceptEmpty, DEFAULT_VM_TARGET, formatBytecodeForDebugging, formatParametersForDebugging, getLockScriptName, getSignatureAndPubkeyFromP2PKHInput, getUnlockScriptName, serialiseTokenDetails } from './utils.js';
3839

3940
// TODO: Add / improve descriptions throughout the template generation
4041

@@ -62,70 +63,6 @@ export const getLibauthTemplate = (
6263
scenarios: generateAllTemplateScenarios(libauthTransaction, transactionBuilder),
6364
};
6465

65-
// TODO: Refactor the below code to not have deep reassignment of scenario.sourceOutputs and scenario.transaction.outputs
66-
67-
// Initialize bytecode mappings, these will be used to map the locking and unlocking scripts and naming the scripts
68-
const unlockingBytecodeToLockingBytecodeParams: Record<string, WalletTemplateScenarioBytecode> = {};
69-
const lockingBytecodeToLockingBytecodeParams: Record<string, WalletTemplateScenarioBytecode> = {};
70-
71-
// We can typecast this because we check that all inputs are standard unlockable at the top of this function
72-
for (const [inputIndex, input] of (transactionBuilder.inputs as StandardUnlockableUtxo[]).entries()) {
73-
if (isContractUnlocker(input.unlocker)) {
74-
const lockScriptName = getLockScriptName(input.unlocker.contract);
75-
if (!lockScriptName) continue;
76-
77-
const lockingScriptParams = generateLockingScriptParams(input.unlocker.contract, input, lockScriptName);
78-
79-
const unlockingBytecode = binToHex(libauthTransaction.inputs[inputIndex].unlockingBytecode);
80-
unlockingBytecodeToLockingBytecodeParams[unlockingBytecode] = lockingScriptParams;
81-
82-
const lockingBytecode = binToHex(addressToLockScript(input.unlocker.contract.address));
83-
lockingBytecodeToLockingBytecodeParams[lockingBytecode] = lockingScriptParams;
84-
}
85-
}
86-
87-
for (const scenario of Object.values(template.scenarios!)) {
88-
// For Inputs
89-
for (const [idx, input] of libauthTransaction.inputs.entries()) {
90-
const unlockingBytecode = binToHex(input.unlockingBytecode);
91-
const lockingBytecodeParams = unlockingBytecodeToLockingBytecodeParams[unlockingBytecode];
92-
93-
// If lockingBytecodeParams is unknown, then it stays at default: {}
94-
if (!lockingBytecodeParams) continue;
95-
96-
// If locking bytecode is set to ['slot'] then this is being evaluated by the scenario, so we don't replace bytecode
97-
if (lockingBytecodeIsSetToSlot(scenario?.sourceOutputs?.[idx]?.lockingBytecode)) continue;
98-
99-
// If lockingBytecodeParams is known, and this input is not ['slot'] then assign a locking bytecode as source output
100-
if (scenario.sourceOutputs?.[idx]) {
101-
scenario.sourceOutputs[idx] = {
102-
...scenario.sourceOutputs[idx],
103-
lockingBytecode: lockingBytecodeParams,
104-
};
105-
}
106-
}
107-
108-
// For Outputs
109-
for (const [idx, output] of libauthTransaction.outputs.entries()) {
110-
const lockingBytecode = binToHex(output.lockingBytecode);
111-
const lockingBytecodeParams = lockingBytecodeToLockingBytecodeParams[lockingBytecode];
112-
113-
// If lockingBytecodeParams is unknown, then it stays at default: {}
114-
if (!lockingBytecodeParams) continue;
115-
116-
// If locking bytecode is set to ['slot'] then this is being evaluated by the scenario, so we don't replace bytecode
117-
if (lockingBytecodeIsSetToSlot(scenario?.transaction?.outputs?.[idx]?.lockingBytecode)) continue;
118-
119-
// If lockingBytecodeParams is known, and this input is not ['slot'] then assign a locking bytecode as source output
120-
if (scenario?.transaction?.outputs?.[idx]) {
121-
scenario.transaction.outputs[idx] = {
122-
...scenario.transaction.outputs[idx],
123-
lockingBytecode: lockingBytecodeParams,
124-
};
125-
}
126-
}
127-
}
128-
12966
return template;
13067
};
13168

@@ -188,6 +125,25 @@ const generateAllTemplateScripts = (
188125
return scripts.reduce((acc, script) => ({ ...acc, ...script }), {});
189126
};
190127

128+
const generateLockingBytecodeParamsMapping = (
129+
transactionBuilder: TransactionBuilder,
130+
): Record<string, WalletTemplateScenarioBytecode> => {
131+
// Initialize bytecode mapping, this will be used to map the locking bytecode to the locking bytecode params
132+
const mapping: Record<string, WalletTemplateScenarioBytecode> = {};
133+
134+
// We can typecast this because we check that all inputs are standard unlockable at the top of this function
135+
for (const input of (transactionBuilder.inputs as StandardUnlockableUtxo[])) {
136+
if (isContractUnlocker(input.unlocker)) {
137+
const lockScriptName = getLockScriptName(input.unlocker.contract);
138+
const lockingScriptParams = generateLockingScriptParams(input.unlocker.contract, input, lockScriptName);
139+
const lockingBytecode = binToHex(addressToLockScript(input.unlocker.contract.address));
140+
mapping[lockingBytecode] = lockingScriptParams;
141+
}
142+
}
143+
144+
return mapping;
145+
};
146+
191147
const generateAllTemplateScenarios = (
192148
libauthTransaction: TransactionBch,
193149
transactionBuilder: TransactionBuilder,
@@ -451,6 +407,8 @@ const generateTemplateScenarioTransaction = (
451407
transactionBuilder: TransactionBuilder,
452408
slotIndex: number,
453409
): WalletTemplateScenario['transaction'] => {
410+
const lockingBytecodeParamsMapping = generateLockingBytecodeParamsMapping(transactionBuilder);
411+
454412
const zippedInputs = zip(transactionBuilder.inputs, libauthTransaction.inputs);
455413
const inputs = zippedInputs.map(([csInput, libauthInput], inputIndex) => {
456414
return {
@@ -465,16 +423,10 @@ const generateTemplateScenarioTransaction = (
465423

466424
const zippedOutputs = zip(transactionBuilder.outputs, libauthTransaction.outputs);
467425
const outputs = zippedOutputs.map(([csOutput, libauthOutput]) => {
468-
if (csOutput && contract) {
469-
return {
470-
lockingBytecode: generateTemplateScenarioTransactionOutputLockingBytecode(csOutput, contract),
471-
token: serialiseTokenDetails(libauthOutput.token),
472-
valueSatoshis: Number(libauthOutput.valueSatoshis),
473-
};
474-
}
475-
476426
return {
477-
lockingBytecode: `${binToHex(libauthOutput.lockingBytecode)}`,
427+
lockingBytecode: generateTemplateScenarioTransactionOutputLockingBytecode(
428+
csOutput, libauthOutput, contract, lockingBytecodeParamsMapping,
429+
),
478430
token: serialiseTokenDetails(libauthOutput.token),
479431
valueSatoshis: Number(libauthOutput.valueSatoshis),
480432
};
@@ -493,7 +445,7 @@ const generateTemplateScenarioSourceOutputs = (
493445
const zippedInputs = zip(transactionBuilder.inputs, libauthTransaction.inputs);
494446
return zippedInputs.map(([csInput, libauthInput], inputIndex) => {
495447
return {
496-
lockingBytecode: generateTemplateScenarioBytecode(csInput, libauthInput, inputIndex, 'p2pkh_placeholder_lock', inputIndex === slotIndex),
448+
lockingBytecode: generateTemplateScenarioBytecodeForSourceOutputs(csInput, libauthInput, inputIndex, 'p2pkh_placeholder_lock', inputIndex === slotIndex),
497449
valueSatoshis: Number(csInput.satoshis),
498450
token: serialiseTokenDetails(csInput.token),
499451
};
@@ -619,12 +571,45 @@ const generateTemplateScenarioBytecode = (
619571
return {};
620572
};
621573

574+
const generateTemplateScenarioBytecodeForSourceOutputs = (
575+
input: Utxo,
576+
libauthInput: Input,
577+
inputIndex: number,
578+
p2pkhScriptNameTemplate: string,
579+
insertSlot?: boolean,
580+
): WalletTemplateScenarioBytecode | ['slot'] => {
581+
if (insertSlot) return ['slot'];
582+
583+
if (isUnlockableUtxo(input) && isStandardUnlockableUtxo(input)) {
584+
// If the input is a contract unlocker, we need to generate the locking bytecode params for the source outputs
585+
if (isContractUnlocker(input.unlocker)) {
586+
const lockScriptName = getLockScriptName(input.unlocker.contract);
587+
const lockingBytecodeParams = generateLockingScriptParams(input.unlocker.contract, input, lockScriptName);
588+
return lockingBytecodeParams;
589+
}
590+
591+
// For a P2PKH unlocker, the sourceOutputs "locking bytecode params" are the same as the unlocking bytecode params
592+
return generateUnlockingScriptParams(input, libauthInput, p2pkhScriptNameTemplate, inputIndex);
593+
}
594+
595+
// 'slot' means that we are currently evaluating this specific input,
596+
// {} means that it is the same script type, but not being evaluated
597+
return {};
598+
};
599+
622600
const generateTemplateScenarioTransactionOutputLockingBytecode = (
623601
csOutput: Output,
624-
contract: Contract,
602+
libauthOutput: LibauthOutput,
603+
contract: Contract | undefined,
604+
lockingBytecodeParamsMapping: Record<string, WalletTemplateScenarioBytecode>,
625605
): string | {} => {
606+
// If lockingBytecodeParams is known from the mapping, return it
607+
const lockingBytecode = binToHex(libauthOutput.lockingBytecode);
608+
const lockingBytecodeParams = lockingBytecodeParamsMapping[lockingBytecode];
609+
if (lockingBytecodeParams) return lockingBytecodeParams;
610+
626611
if (csOutput.to instanceof Uint8Array) return binToHex(csOutput.to);
627-
if ([contract.address, contract.tokenAddress].includes(csOutput.to)) return {};
612+
if (contract && [contract.address, contract.tokenAddress].includes(csOutput.to)) return {};
628613
return binToHex(addressToLockScript(csOutput.to));
629614
};
630615

packages/cashscript/src/libauth-template/utils.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AbiFunction, AbiInput, Artifact, bytecodeToScript, formatBitAuthScript } from '@cashscript/utils';
22
import { HashType, LibauthTokenDetails, SignatureAlgorithm, TokenDetails, VmTarget } from '../interfaces.js';
3-
import { hexToBin, binToHex, isHex, decodeCashAddress, type WalletTemplateScenarioBytecode, Input, assertSuccess, decodeAuthenticationInstructions, AuthenticationInstructionPush } from '@bitauth/libauth';
3+
import { hexToBin, binToHex, isHex, decodeCashAddress, Input, assertSuccess, decodeAuthenticationInstructions, AuthenticationInstructionPush } from '@bitauth/libauth';
44
import { EncodedFunctionArgument } from '../Argument.js';
55
import { zip } from '../utils.js';
66
import SignatureTemplate from '../SignatureTemplate.js';
@@ -111,10 +111,6 @@ interface LibauthTemplateTokenDetails {
111111
};
112112
}
113113

114-
export const lockingBytecodeIsSetToSlot = (lockingBytecode?: WalletTemplateScenarioBytecode | ['slot']): boolean => {
115-
return Array.isArray(lockingBytecode) && lockingBytecode.length === 1 && lockingBytecode[0] === 'slot';
116-
};
117-
118114
export const getSignatureAndPubkeyFromP2PKHInput = (
119115
libauthInput: Input,
120116
): { signature: Uint8Array; publicKey: Uint8Array } => {

0 commit comments

Comments
 (0)