Skip to content

Commit

Permalink
Merge pull request #29 from ilap/main
Browse files Browse the repository at this point in the history
Added initial support for recursive PlutusData types and fixed kupmiosv5 provider's script converting issue caused my previous PR
  • Loading branch information
micahkendall authored Jan 17, 2024
2 parents e7b27c9 + 08f5990 commit 4dd6903
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 85 deletions.
Binary file modified bun.lockb
Binary file not shown.
52 changes: 40 additions & 12 deletions src/plutus/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,11 @@ export const Data = {
* Convert PlutusData to Cbor encoded data.\
* Or apply a shape and convert the provided data struct to Cbor encoded data.
*/
function to<T = Data>(data: Exact<T>, type?: T): Datum | Redeemer {
function to<T = Data>(
data: Exact<T>,
type?: T,
recType?: string,
): Datum | Redeemer {
function serialize(data: Data): C.PlutusData {
try {
if (typeof data === "bigint") {
Expand Down Expand Up @@ -301,7 +305,7 @@ function to<T = Data>(data: Exact<T>, type?: T): Datum | Redeemer {
throw new Error("Could not serialize the data: " + error);
}
}
const d = type ? castTo<T>(data, type) : (data as Data);
const d = type ? castTo<T>(data, type, recType) : (data as Data);
return toHex(serialize(d).to_bytes()) as Datum | Redeemer;
}

Expand Down Expand Up @@ -618,11 +622,26 @@ function castFrom<T = Data>(data: Data, type: T): T {
throw new Error("Could not type cast data.");
}

function castTo<T>(struct: Exact<T>, type: T): Data {
const shape = type as Json;
function castTo<T>(
struct: Exact<T>,
type: T,
recType?: string,
recShape?: {
recType: string;
shape: T;
shapeType: string;
},
): Data {
let shape = type as Json;
if (!shape) throw new Error("Could not type cast struct.");
const shapeType = (shape.anyOf ? "enum" : "") || shape.dataType;
let shapeType = (shape.anyOf ? "enum" : "") || shape.dataType;

if (recType === shape.title) {
recShape = { recType: recType!, shape: shape, shapeType: shapeType };
} else if (recShape && shape.$ref) {
shape = recShape.shape;
shapeType = recShape.shapeType;
}
switch (shapeType) {
case "integer": {
if (typeof struct !== "bigint") {
Expand Down Expand Up @@ -655,6 +674,8 @@ function castTo<T>(struct: Exact<T>, type: T): Data {
castTo<T>(
(struct as Record<string, Json>)[field.title || "wrapper"],
field,
recType,
recShape,
),
);
return shape.hasConstr || shape.hasConstr === undefined
Expand All @@ -664,7 +685,7 @@ function castTo<T>(struct: Exact<T>, type: T): Data {
case "enum": {
// When enum has only one entry it's a single constructor/record object
if (shape.anyOf.length === 1) {
return castTo<T>(struct, shape.anyOf[0]);
return castTo<T>(struct, shape.anyOf[0], recType, recShape);
}

if (isBoolean(shape)) {
Expand All @@ -679,7 +700,9 @@ function castTo<T>(struct: Exact<T>, type: T): Data {
if (fields.length !== 1) {
throw new Error("Could not type cast to nullable object.");
}
return new Constr(0, [castTo<T>(struct, fields[0])]);
return new Constr(0, [
castTo<T>(struct, fields[0], recType, recShape),
]);
}
}
switch (typeof struct) {
Expand Down Expand Up @@ -721,13 +744,13 @@ function castTo<T>(struct: Exact<T>, type: T): Data {
// check if named args
args instanceof Array
? args.map((item, index) =>
castTo<T>(item, enumEntry.fields[index]),
castTo<T>(item, enumEntry.fields[index], recType, recShape),
)
: enumEntry.fields.map((entry: Json) => {
const [_, item]: [string, Json] = Object.entries(args).find(
([title]) => title === entry.title,
)!;
return castTo<T>(item, entry);
return castTo<T>(item, entry, recType, recShape);
}),
);
}
Expand All @@ -741,13 +764,15 @@ function castTo<T>(struct: Exact<T>, type: T): Data {
if (shape.items instanceof Array) {
// tuple
const fields = struct.map((item, index) =>
castTo<T>(item, shape.items[index]),
castTo<T>(item, shape.items[index], recType, recShape),
);
return shape.hasConstr ? new Constr(0, fields) : fields;
} else {
// array
listConstraints(struct, shape);
return struct.map((item) => castTo<T>(item, shape.items));
return struct.map((item) =>
castTo<T>(item, shape.items, recType, recShape),
);
}
}
case "map": {
Expand All @@ -759,7 +784,10 @@ function castTo<T>(struct: Exact<T>, type: T): Data {

const map = new Map<Data, Data>();
for (const [key, value] of struct.entries()) {
map.set(castTo<T>(key, shape.keys), castTo<T>(value, shape.values));
map.set(
castTo<T>(key, shape.keys, recType, recShape),
castTo<T>(value, shape.values, recType, recShape),
);
}
return map;
}
Expand Down
8 changes: 6 additions & 2 deletions src/provider/kupmiosv5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,16 @@ export class KupmiosV5 implements Provider {
} else if (language === "plutus:v1") {
return {
type: "PlutusV1",
script: toHex(C.PlutusScript.new(fromHex(script)).to_bytes()),
script: toHex(
C.PlutusV1Script.new(fromHex(script)).to_bytes(),
),
};
} else if (language === "plutus:v2") {
return {
type: "PlutusV2",
script: toHex(C.PlutusScript.new(fromHex(script)).to_bytes()),
script: toHex(
C.PlutusV2Script.new(fromHex(script)).to_bytes(),
),
};
}
})()),
Expand Down
65 changes: 38 additions & 27 deletions src/translucent/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,50 +924,61 @@ export class Tx {
BigInt(slotConfig.zeroSlot),
slotConfig.slotLength,
);
const redeemers = C.Redeemers.new()
const redeemers = C.Redeemers.new();
for (const redeemerBytes of uplcResults) {
let redeemer: C.Redeemer = C.Redeemer.from_bytes(redeemerBytes);
this.txBuilder.set_exunits(
C.RedeemerWitnessKey.new(redeemer.tag(), redeemer.index()),
redeemer.ex_units(),
);
redeemers.add(redeemer)
redeemers.add(redeemer);
}
let builtTx = this.txBuilder.build(0, changeAddress).build_unchecked();
{
const datums = C.PlutusList.new()
const unhashedData = builtTx.witness_set().plutus_data()
let hashes = []
if (unhashedData){
for (let i=0; i<unhashedData.len(), i++;){
hashes.push(C.hash_plutus_data(unhashedData.get(i)).to_hex())
const datums = C.PlutusList.new();
const unhashedData = builtTx.witness_set().plutus_data();
let hashes = [];
if (unhashedData) {
for (let i = 0; i < unhashedData.len(), i++; ) {
hashes.push(C.hash_plutus_data(unhashedData.get(i)).to_hex());
}
}
for (let i=0; i<builtTx.body().inputs().len(), i++;) {
for (let i = 0; i < builtTx.body().inputs().len(), i++; ) {
const input = builtTx.body().inputs().get(i);
const utxo = allUtxos.find((utxo)=>utxo.input().to_bytes()==input.to_bytes())
const datum = utxo?.output().datum()
if (datum){
const inline = datum.as_inline_data()
if (inline){
datums.add(inline)
}else{
const utxo = allUtxos.find(
(utxo) => utxo.input().to_bytes() == input.to_bytes(),
);
const datum = utxo?.output().datum();
if (datum) {
const inline = datum.as_inline_data();
if (inline) {
datums.add(inline);
} else {
const hash = datum.as_data_hash();
if (hash){
const idx = hashes.indexOf(hash.to_hex())
const data = unhashedData?.get(idx)!
datums.add(data)
if (hash) {
const idx = hashes.indexOf(hash.to_hex());
const data = unhashedData?.get(idx)!;
datums.add(data);
}
}
}
}
const languages = C.Languages.new()
languages.add(C.Language.new_plutus_v2())
const sdh = C.calc_script_data_hash(redeemers, datums, costMdls, languages)
if (sdh){
const bodyWithDataHash = builtTx.body()
bodyWithDataHash.set_script_data_hash(sdh)
builtTx = C.Transaction.new(bodyWithDataHash, builtTx.witness_set(), builtTx.auxiliary_data())
const languages = C.Languages.new();
languages.add(C.Language.new_plutus_v2());
const sdh = C.calc_script_data_hash(
redeemers,
datums,
costMdls,
languages,
);
if (sdh) {
const bodyWithDataHash = builtTx.body();
bodyWithDataHash.set_script_data_hash(sdh);
builtTx = C.Transaction.new(
bodyWithDataHash,
builtTx.witness_set(),
builtTx.auxiliary_data(),
);
}
}
return new TxComplete(this.translucent, builtTx);
Expand Down
81 changes: 43 additions & 38 deletions src/wallets/chained.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,81 +12,86 @@ import {
utxoToCore,
fromHex,
coreToUtxo,
} from '../mod'
import { AbstractWallet } from './abstract'
} from "../mod";
import { AbstractWallet } from "./abstract";

export class ChainedWallet implements AbstractWallet {
translucent: Translucent
wallet: AbstractWallet
utxos: UTxO[] = []
translucent: Translucent;
wallet: AbstractWallet;
utxos: UTxO[] = [];

constructor(translucent: Translucent, wallet: AbstractWallet) {
this.translucent = translucent
this.wallet = wallet
wallet.getUtxos().then((utxos) => (this.utxos = utxos))
this.translucent = translucent;
this.wallet = wallet;
wallet.getUtxos().then((utxos) => (this.utxos = utxos));
}

async refreshUtxos() {
this.utxos = await this.wallet.getUtxos()
this.utxos = await this.wallet.getUtxos();
}

async chain(tx: Transaction, predicate: (utxo: UTxO)=>boolean){
const txCore = C.Transaction.from_bytes(fromHex(tx))
const hash = C.hash_transaction(txCore.body())
const inputs = txCore.body().inputs()
const outputs = txCore.body().outputs()
const toConsume: Record<string, boolean> = {}
for (let i=0; i<inputs.len(); i++){
const input = inputs.get(i);
toConsume[input.transaction_id().to_hex() + input.index()] = true
async chain(tx: Transaction, predicate: (utxo: UTxO) => boolean) {
const txCore = C.Transaction.from_bytes(fromHex(tx));
const hash = C.hash_transaction(txCore.body());
const inputs = txCore.body().inputs();
const outputs = txCore.body().outputs();
const toConsume: Record<string, boolean> = {};
for (let i = 0; i < inputs.len(); i++) {
const input = inputs.get(i);
toConsume[input.transaction_id().to_hex() + input.index()] = true;
}
this.utxos = this.utxos.filter(
(utxo) => toConsume[utxo.txHash + utxo.outputIndex.toString()] == true,
);
for (let i = 0; i < outputs.len(); i++) {
const output = C.TransactionUnspentOutput.new(
C.TransactionInput.new(hash, C.BigNum.from_str(i.toString())),
outputs.get(i),
);
const utxo = coreToUtxo(output);
if (predicate(utxo)) {
this.utxos.push(utxo);
}
}
this.utxos = this.utxos.filter((utxo)=>toConsume[utxo.txHash+utxo.outputIndex.toString()]==true)
for (let i=0; i<outputs.len(); i++){
const output = C.TransactionUnspentOutput.new(C.TransactionInput.new(hash, C.BigNum.from_str(i.toString())), outputs.get(i))
const utxo = coreToUtxo(output);
if (predicate(utxo)){
this.utxos.push(utxo);
};
};
}

address(): Promise<Address> {
return this.wallet.address()
return this.wallet.address();
}

rewardAddress(): Promise<RewardAddress | null> {
return this.wallet.rewardAddress()
return this.wallet.rewardAddress();
}

getUtxos(): Promise<UTxO[]> {
return Promise.resolve(this.utxos)
return Promise.resolve(this.utxos);
}

getUtxosCore(): Promise<C.TransactionUnspentOutputs> {
const outputs = C.TransactionUnspentOutputs.new()
const utxos = this.utxos.map(utxoToCore)
for (const utxo of utxos){
outputs.add(utxo)
const outputs = C.TransactionUnspentOutputs.new();
const utxos = this.utxos.map(utxoToCore);
for (const utxo of utxos) {
outputs.add(utxo);
}
return Promise.resolve(outputs)
return Promise.resolve(outputs);
}

getDelegation(): Promise<Delegation> {
return this.wallet.getDelegation()
return this.wallet.getDelegation();
}

signTx(tx: C.Transaction): Promise<C.TransactionWitnessSet> {
return this.wallet.signTx(tx)
return this.wallet.signTx(tx);
}

signMessage(
address: Address | RewardAddress,
payload: Payload,
): Promise<SignedMessage> {
return this.wallet.signMessage(address, payload)
return this.wallet.signMessage(address, payload);
}

submitTx(signedTx: Transaction): Promise<TxHash> {
return this.wallet.submitTx(signedTx)
return this.wallet.submitTx(signedTx);
}
}
12 changes: 6 additions & 6 deletions src/wallets/mod.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from "./abstract"
export * from "./chained"
export * from "./private_key"
export * from "./public_wallet"
export * from "./seed"
export * from "./wallet_connector"
export * from "./abstract";
export * from "./chained";
export * from "./private_key";
export * from "./public_wallet";
export * from "./seed";
export * from "./wallet_connector";

0 comments on commit 4dd6903

Please sign in to comment.