Skip to content

Commit

Permalink
v0.5.4 - offchain v0.1.7 - txBuilder time bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
michele-nuzzi committed Sep 3, 2023
1 parent 080798f commit 09e0d18
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 30 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@harmoniclabs/plu-ts",
"version": "0.5.3",
"version": "0.5.4",
"description": "An embedded DSL for Cardano smart contracts creation coupled with a library for Cardano transactions, all in Typescript",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down Expand Up @@ -63,7 +63,7 @@
"@harmoniclabs/uplc": "^1.1.0",
"@harmoniclabs/cardano-ledger-ts": "^0.1.2",
"@harmoniclabs/plu-ts-onchain": "^0.2.4",
"@harmoniclabs/plu-ts-offchain": "^0.1.6"
"@harmoniclabs/plu-ts-offchain": "^0.1.7"
},
"devDependencies": {
"@babel/preset-env": "^7.18.6",
Expand Down
2 changes: 1 addition & 1 deletion packages/offchain/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@harmoniclabs/plu-ts-offchain",
"version": "0.1.6",
"version": "0.1.7",
"description": "An embedded DSL for Cardano smart contracts creation coupled with a library for Cardano transactions, all in Typescript",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
11 changes: 9 additions & 2 deletions packages/offchain/src/TxBuilder/GenesisInfos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ import { CanBeUInteger, canBeUInteger } from "../utils/ints";


export interface GenesisInfos {
systemStartPOSIX: CanBeUInteger,
slotLengthInMilliseconds: CanBeUInteger
/**
* ### POSIX timestamp of blockchain start
* ## with **milliseconds precision**
**/
readonly systemStartPOSIX: CanBeUInteger,
/**
* ### slot duration in **milliseconds**
**/
readonly slotLengthInMilliseconds: CanBeUInteger
}

export function isGenesisInfos( stuff: any ): stuff is GenesisInfos
Expand Down
122 changes: 105 additions & 17 deletions packages/offchain/src/TxBuilder/TxBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { fromUtf8, isUint8Array, lexCompare, toHex } from "@harmoniclabs/uint8array-utils";
import { fromHex, fromUtf8, isUint8Array, lexCompare, toHex } from "@harmoniclabs/uint8array-utils";
import { keepRelevant } from "./keepRelevant";
import { GenesisInfos, isGenesisInfos } from "./GenesisInfos";
import { isCostModelsV2, isCostModelsV1, defaultV2Costs, defaultV1Costs, costModelsToLanguageViewCbor } from "@harmoniclabs/cardano-costmodels-ts";
import { NetworkT, ProtocolParamters, isPartialProtocolParameters, Tx, Value, ValueUnits, TxOut, TxRedeemerTag, txRdmrTagToString, ScriptType, UTxO, VKeyWitness, Script, BootstrapWitness, TxRedeemer, Hash32, TxIn, Hash28, AuxiliaryData, TxWitnessSet, getNSignersNeeded, txRedeemerTagToString, ScriptDataHash, Address, AddressStr } from "@harmoniclabs/cardano-ledger-ts";
import { CborString, CborPositiveRational, Cbor, CborArray } from "@harmoniclabs/cbor";
import { CborString, CborPositiveRational, Cbor, CborArray, CanBeCborString } from "@harmoniclabs/cbor";
import { byte, blake2b_256 } from "@harmoniclabs/crypto";
import { Data, dataToCborObj, DataConstr, dataToCbor } from "@harmoniclabs/plutus-data";
import { machineVersionV2, machineVersionV1, Machine, ExBudget } from "@harmoniclabs/plutus-machine";
import { UPLCTerm, UPLCDecoder, Application, UPLCConst, ErrorUPLC } from "@harmoniclabs/uplc";
import { POSIXToSlot, getTxInfos, slotToPOSIX } from "../toOnChain";
import { ITxBuildArgs, ITxBuildOptions, ITxBuildInput, ITxBuildSyncOptions, txBuildOutToTxOut, ChangeInfos } from "../txBuild";
import { CanBeUInteger, forceBigUInt, canBeUInteger, unsafeForceUInt } from "../utils/ints";
import { freezeAll, defineReadOnlyProperty, definePropertyIfNotPresent, hasOwn } from "@harmoniclabs/obj-utils";
import { freezeAll, defineReadOnlyProperty, definePropertyIfNotPresent, hasOwn, isObject } from "@harmoniclabs/obj-utils";
import { assert } from "../utils/assert";
import { TxBuilderRunner } from "./TxBuilderRunner/TxBuilderRunner";
import { ITxRunnerProvider } from "./IProvider";
Expand Down Expand Up @@ -69,7 +69,7 @@ export class TxBuilder
)
{
let _genesisInfos: GenesisInfos | undefined = undefined;
const _setGenesisInfos = ( genInfos: GenesisInfos ) => {
const _setGenesisInfos = ( genInfos: GenesisInfos ): void => {
if( !isGenesisInfos( genInfos ) ) return;

_genesisInfos = freezeAll( genInfos );
Expand Down Expand Up @@ -169,6 +169,29 @@ export class TxBuilder
);
}

getMinimumOutputLovelaces( tx_out: TxOut | CanBeCborString ): bigint
{
if( tx_out instanceof TxOut ) tx_out = tx_out.toCbor().toBuffer();
if(!(tx_out instanceof Uint8Array))
{
if(
isObject( tx_out ) &&
hasOwn( tx_out, "toBuffer" ) &&
typeof tx_out.toBuffer === "function"
)
tx_out = tx_out.toBuffer();

if(!(tx_out instanceof Uint8Array)) tx_out = fromHex( tx_out.toString() );
}
const size = BigInt( tx_out.length );
return BigInt( this.protocolParamters.utxoCostPerByte ) * size;
}

/**
*
* @param slotN number of the slot
* @returns POSIX time in **milliseconds**
*/
slotToPOSIX( slot: CanBeUInteger ): number
{
const gInfos = this.genesisInfos;
Expand All @@ -184,6 +207,10 @@ export class TxBuilder
)
}

/**
*
* @param POSIX POSIX time in milliseconds
*/
posixToSlot( POSIX: CanBeUInteger ): number
{
const gInfos = this.genesisInfos;
Expand Down Expand Up @@ -228,12 +255,15 @@ export class TxBuilder
change
} = initTxBuild.bind( this )( buildArgs );

const txOuts: TxOut[] = new Array( outs.length + 1 );

const rdmrs = tx.witnesses.redeemers ?? [];
const nRdmrs = rdmrs.length;

if( nRdmrs === 0 ) return tx;
if( nRdmrs === 0 ){
this.assertMinOutLovelaces( tx.body.outputs );
return tx
};

const txOuts: TxOut[] = new Array( outs.length + 1 );

const cek: Machine = (this as any).cek;

Expand Down Expand Up @@ -439,8 +469,29 @@ export class TxBuilder
totExBudget = new ExBudget({ mem: 0, cpu: 0 })
}

this.assertMinOutLovelaces( tx.body.outputs );

return tx;
}

assertMinOutLovelaces( txOuts: TxOut[] ): void
{
for(let i = 0; i < txOuts.length; i++)
{
const out = txOuts[i];
const minLovelaces = this.getMinimumOutputLovelaces( out );

if( out.value.lovelaces < minLovelaces )
throw new Error(
`tx output at index ${i} did not have enough lovelaces to meet the minimum allowed by protocol parameters.\n` +
`output size: ${out.toCbor().toBuffer().length} bytes\n` +
`protocol paramters "utxoCostPerByte": ${this.protocolParamters.utxoCostPerByte}\n` +
`minimum lovelaces required: ${minLovelaces.toString()}\n` +
`output lovelaces : ${out.value.lovelaces.toString()}\n` +
`tx output: ${JSON.stringify( out.toJson(), undefined, 2 )}`
);
}
}
}

type ScriptToExecEntry = {
Expand Down Expand Up @@ -924,18 +975,58 @@ function initTxBuild(

invalidBefore = invalidBefore === undef ? undef : forceBigUInt( invalidBefore );

if( invalidAfter !== undef )
{
if( invalidBefore === undef ) invalidBefore = 0;
}
// if( invalidAfter !== undef )
// {
// if( invalidBefore === undef ) invalidBefore = 0;
// }

if(
canBeUInteger( invalidBefore ) &&
canBeUInteger( invalidAfter )
)
{
if( invalidBefore >= invalidAfter )
throw new Error("invalid validity interval; invalidAfter: " + invalidAfter.toString() + "; was smaller (previous poin in time) than invalidBefore:" + invalidBefore.toString() );
throw new Error(
"invalid validity interval; invalidAfter: "
+ invalidAfter.toString() +
"; was smaller (previous point in time) than invalidBefore:"
+ invalidBefore.toString()
);
}

// assert collateral is present if needed
if( scriptsToExec.filter( s => s.script.type !== "NativeScript" ).length > 0 )
{
if(
!Array.isArray( collaterals ) ||
collaterals.length <= 0
)
throw new Error("tx includes plutus scripts to execute but no collateral input was provided");

const collateralValue = collaterals.reduce<Value>(
(accum, collateral) => Value.add( accum, collateral.resolved.value ),
Value.zero
);

if( !Value.isAdaOnly( collateralValue ) )
{
if( !collateralReturn )
throw new Error(
`total collateral input value was including non-ADA value; no collateral return was specified\n` +
`total collateral input value was: ${JSON.stringify( collateralValue.toJson(), undef, 2 )}`
);

const realCollValue = Value.sub(
collateralValue,
collateralReturn.value
);

if( !Value.isAdaOnly( realCollValue ) )
throw new Error(
`total collateral value was including non-ADA value;\n` +
`total collateral value was: ${JSON.stringify( realCollValue.toJson(), undef, 2 )}`
);
}
}

const dummyTx = new Tx({
Expand All @@ -960,12 +1051,9 @@ function initTxBuild(
undef :
forceBigUInt( invalidBefore ),
ttl:
(
invalidBefore === undef ||
invalidAfter === undef
) ?
invalidAfter === undef ?
undef :
forceBigUInt( invalidAfter ) - forceBigUInt( invalidBefore ),
forceBigUInt( invalidAfter ),
auxDataHash: auxData?.hash,
scriptDataHash: getScriptDataHash( redeemers, datumsScriptData, languageViews ),
network
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ describe("TxBuilderRunner", () => {
}),
new DataI( 0 )
)
.setCollateral(
new UTxO({
utxoRef: {
id: "33".repeat( 32 ),
index: 0
},
resolved: {
address: Address.fake,
value: Value.lovelaces( 10_000_000 )
}
})
)
.attachValidator( okScript )
.payTo( Address.fake, 5_000_000 )
.build();
Expand Down
74 changes: 69 additions & 5 deletions packages/offchain/src/__tests__/TxBuilder.build.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,75 @@
import { defaultProtocolParameters, Address, PaymentCredentials, PubKeyHash, Script, ScriptType, UTxO, Value, getNSignersNeeded, Tx } from "@harmoniclabs/cardano-ledger-ts";
import { Cbor, CborBytes } from "@harmoniclabs/cbor";
import { DataConstr, DataList, DataI } from "@harmoniclabs/plutus-data";
import { fromAscii } from "@harmoniclabs/uint8array-utils";
import { defaultProtocolParameters, Address, UTxO, Value, TxOutRef, TxOut } from "@harmoniclabs/cardano-ledger-ts";
import { TxBuilder } from "../TxBuilder"

test.todo("depends on onchain");

describe("build time", () => {

const txBuilder = new TxBuilder(
defaultProtocolParameters
);

const txBuilderWithGenesis = new TxBuilder(
defaultProtocolParameters,
{
slotLengthInMilliseconds: 1000,
systemStartPOSIX: (Math.round( Date.now() / 1e3 ) * 1e3) - 1e6
}
);

test("assert min out lovelaces", () => {

expect(
() => {
txBuilder.buildSync({
inputs: [
{
utxo: new UTxO({
utxoRef: TxOutRef.fake,
resolved: new TxOut({
address: Address.fake,
value: Value.lovelaces( 10_000_000 )
})
})
}
],
outputs: [
{
address: Address.fake,
value: Value.zero
}
],
changeAddress: Address.fake
});
}
).toThrow()

expect(
() => {
txBuilder.buildSync({
inputs: [
{
utxo: new UTxO({
utxoRef: TxOutRef.fake,
resolved: new TxOut({
address: Address.fake,
value: Value.lovelaces( 10_000_000 )
})
})
}
],
outputs: [
{
address: Address.fake,
value: Value.lovelaces( 1_500_000 )
}
],
changeAddress: Address.fake
});
}
).not.toThrow()
})

})

/*
jest.setTimeout(2_000_000)
Expand Down
6 changes: 3 additions & 3 deletions packages/offchain/src/__tests__/TxBuilder.build.time.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe("build time", () => {
});

expect( tx.body.validityIntervalStart )
.not.toBe( undefined );
.toBe( undefined );

expect( tx.body.ttl )
.not.toBe( undefined );
Expand Down Expand Up @@ -97,7 +97,7 @@ describe("build time", () => {
.not.toBe( undefined );

expect( tx.body.ttl )
.toBe( 69n - 42n );
.toBe( 69n );

});

Expand Down Expand Up @@ -233,4 +233,4 @@ describe("build time", () => {
});
*/

})
});
14 changes: 14 additions & 0 deletions packages/offchain/src/toOnChain/getTxIntervalData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,25 @@ import { Data, DataConstr, DataI } from "@harmoniclabs/plutus-data";
import { GenesisInfos, isGenesisInfos } from "../TxBuilder/GenesisInfos";
import { unsafeForceUInt } from "../utils/ints";

/**
*
* @param POSIX POSIX time in milliseconds
* @param sysStartPOSIX blockchain start POSIX time in milliseconds
* @param slotLenMs milliseconds per slot
* @returns
*/
export function POSIXToSlot( POSIX: number, sysStartPOSIX: number, slotLenMs: number ): number
{
return Math.floor( (POSIX - sysStartPOSIX) / slotLenMs );
}

/**
*
* @param slotN number of the slot
* @param sysStartPOSIX blockchain start POSIX time in milliseconds
* @param slotLenMs milliseconds per slot
* @returns
*/
export function slotToPOSIX( slotN: number, sysStartPOSIX: number, slotLenMs: number ): number
{
return sysStartPOSIX + (slotLenMs * slotN);
Expand Down

0 comments on commit 09e0d18

Please sign in to comment.