Skip to content

Commit

Permalink
feat: Add origin post-condition principals
Browse files Browse the repository at this point in the history
  • Loading branch information
janniks committed Sep 17, 2024
1 parent 7a986ba commit e7a1ddc
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 17 deletions.
19 changes: 17 additions & 2 deletions packages/transactions/src/pc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ export function principal(principal: AddressString | ContractIdString) {
return new PartialPcWithPrincipal(principal);
}

/**
* ### `Pc.` Post Condition Builder
* @beta Interface may be subject to change in future releases.
* @returns A partial post condition builder, which can be chained into a final post condition.
* @example
* ```
* import { Pc } from '@stacks/transactions';
* Pc.origin().willSendEq(10000).ustx();
* Pc.origin().willSendGte(2000).ft();
* ```
*/
export function origin() {
return new PartialPcWithPrincipal('origin');
}

/**
* Not meant to be used directly. Start from `Pc.principal(…)` instead.
*/
Expand Down Expand Up @@ -202,7 +217,7 @@ class PartialPcFtWithCode {
*/
class PartialPcNftWithCode {
constructor(
private principal: string,
private address: string,
private code: NonFungibleComparator
) {}

Expand Down Expand Up @@ -234,7 +249,7 @@ class PartialPcNftWithCode {

return {
type: 'nft-postcondition',
address: this.principal,
address: this.address,
condition: this.code,
asset: `${contractAddress}.${contractName}::${tokenName}`,
assetId,
Expand Down
22 changes: 18 additions & 4 deletions packages/transactions/src/postcondition.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { FungibleConditionCode, NonFungibleConditionCode, PostConditionType } from './constants';
import {
FungibleConditionCode,
NonFungibleConditionCode,
PostConditionPrincipalId,
PostConditionType,
} from './constants';
import { PostCondition } from './postcondition-types';
import {
PostConditionWire,
Expand Down Expand Up @@ -28,15 +33,21 @@ export function postConditionToWire(postcondition: PostCondition): PostCondition
return {
type: StacksWireType.PostCondition,
conditionType: PostConditionType.STX,
principal: parsePrincipalString(postcondition.address),
principal:
postcondition.address === 'origin'
? { type: StacksWireType.Principal, prefix: PostConditionPrincipalId.Origin }
: parsePrincipalString(postcondition.address),
conditionCode: FUNGIBLE_COMPARATOR_MAPPING[postcondition.condition],
amount: BigInt(postcondition.amount),
};
case 'ft-postcondition':
return {
type: StacksWireType.PostCondition,
conditionType: PostConditionType.Fungible,
principal: parsePrincipalString(postcondition.address),
principal:
postcondition.address === 'origin'
? { type: StacksWireType.Principal, prefix: PostConditionPrincipalId.Origin }
: parsePrincipalString(postcondition.address),
conditionCode: FUNGIBLE_COMPARATOR_MAPPING[postcondition.condition],
amount: BigInt(postcondition.amount),
asset: parseAssetString(postcondition.asset),
Expand All @@ -45,7 +56,10 @@ export function postConditionToWire(postcondition: PostCondition): PostCondition
return {
type: StacksWireType.PostCondition,
conditionType: PostConditionType.NonFungible,
principal: parsePrincipalString(postcondition.address),
principal:
postcondition.address === 'origin'
? { type: StacksWireType.Principal, prefix: PostConditionPrincipalId.Origin }
: parsePrincipalString(postcondition.address),
conditionCode: NON_FUNGIBLE_COMPARATOR_MAPPING[postcondition.condition],
asset: parseAssetString(postcondition.asset),
assetName: postcondition.assetId,
Expand Down
11 changes: 10 additions & 1 deletion packages/transactions/src/wire/serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
LengthPrefixedStringWire,
MemoStringWire,
MessageSignatureWire,
OriginPrincipalWire,
PayloadInput,
PayloadWire,
PostConditionPrincipalWire,
Expand Down Expand Up @@ -173,7 +174,12 @@ export function serializePrincipal(principal: PostConditionPrincipalWire): strin
export function serializePrincipalBytes(principal: PostConditionPrincipalWire): Uint8Array {
const bytesArray = [];
bytesArray.push(principal.prefix);
bytesArray.push(serializeAddressBytes(principal.address));
if (
principal.prefix === PostConditionPrincipalId.Standard ||
principal.prefix === PostConditionPrincipalId.Contract
) {
bytesArray.push(serializeAddressBytes(principal.address));
}
if (principal.prefix === PostConditionPrincipalId.Contract) {
bytesArray.push(serializeLPStringBytes(principal.contractName));
}
Expand All @@ -193,6 +199,9 @@ export function deserializePrincipalBytes(
const prefix = bytesReader.readUInt8Enum(PostConditionPrincipalId, n => {
throw new DeserializationError(`Unexpected Principal payload type: ${n}`);
});
if (prefix === PostConditionPrincipalId.Origin) {
return { type: StacksWireType.Principal, prefix } as OriginPrincipalWire;
}
const address = deserializeAddressBytes(bytesReader);
if (prefix === PostConditionPrincipalId.Standard) {
return { type: StacksWireType.Principal, prefix, address } as StandardPrincipalWire;
Expand Down
11 changes: 10 additions & 1 deletion packages/transactions/src/wire/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ export interface TenureChangePayloadWire {
readonly publicKeyHash: string;
}

/** @ignore */
export interface OriginPrincipalWire {
readonly type: StacksWireType.Principal;
readonly prefix: PostConditionPrincipalId.Origin;
}

/** @ignore */
export interface StandardPrincipalWire {
readonly type: StacksWireType.Principal;
Expand Down Expand Up @@ -262,7 +268,10 @@ export type PostConditionWire =
| NonFungiblePostConditionWire;

/** @ignore */
export type PostConditionPrincipalWire = StandardPrincipalWire | ContractPrincipalWire;
export type PostConditionPrincipalWire =
| OriginPrincipalWire
| StandardPrincipalWire
| ContractPrincipalWire;

export interface TransactionAuthFieldWire {
type: StacksWireType.TransactionAuthField;
Expand Down
12 changes: 12 additions & 0 deletions packages/transactions/tests/pc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,4 +405,16 @@ describe('pc -- post condition builder', () => {
});
});
});

describe('origin principal', () => {
test('origin string representation', () => {
const pc = Pc.origin().willSendEq(12_345).ustx();
expect(pc).toEqual({
type: 'stx-postcondition',
address: 'origin',
condition: 'eq',
amount: '12345',
});
});
});
});
51 changes: 42 additions & 9 deletions packages/transactions/tests/postcondition.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import {
PostConditionType,
FungibleConditionCode,
NonFungibleConditionCode,
PostConditionPrincipalId,
} from '../src/constants';
import { serializeDeserialize } from './macros';
import { bufferCVFromString, BufferCV } from '../src/clarity';
import { bytesToUtf8, hexToBytes } from '@stacks/common';
import { postConditionToHex, postConditionToWire } from '../src/postcondition';
import {
Cl,
ContractPrincipalWire,
FungiblePostConditionWire,
NonFungiblePostConditionWire,
PostConditionWire,
STXPostConditionWire,
StacksWireType,
addressToString,
deserializeTransaction,
} from '../src';
import { BufferCV, bufferCVFromString } from '../src/clarity';
import {
FungibleConditionCode,
NonFungibleConditionCode,
PostConditionPrincipalId,
PostConditionType,
} from '../src/constants';
import { postConditionToHex, postConditionToWire } from '../src/postcondition';
import { serializeDeserialize } from './macros';

test('STX post condition serialization and deserialization', () => {
const postConditionType = PostConditionType.STX;
Expand All @@ -39,6 +41,7 @@ test('STX post condition serialization and deserialization', () => {
) as STXPostConditionWire;
expect(deserialized.conditionType).toBe(postConditionType);
expect(deserialized.principal.prefix).toBe(PostConditionPrincipalId.Standard);
if (!('address' in deserialized.principal)) throw TypeError;
expect(addressToString(deserialized.principal.address)).toBe(address);
expect(deserialized.conditionCode).toBe(conditionCode);
expect(deserialized.amount.toString()).toBe(amount.toString());
Expand Down Expand Up @@ -70,6 +73,7 @@ test('Fungible post condition serialization and deserialization', () => {
) as FungiblePostConditionWire;
expect(deserialized.conditionType).toBe(postConditionType);
expect(deserialized.principal.prefix).toBe(PostConditionPrincipalId.Standard);
if (!('address' in deserialized.principal)) throw TypeError;
expect(addressToString(deserialized.principal.address)).toBe(address);
expect(deserialized.conditionCode).toBe(conditionCode);
expect(deserialized.amount.toString()).toBe(amount.toString());
Expand Down Expand Up @@ -106,6 +110,7 @@ test('Non-fungible post condition serialization and deserialization', () => {
) as NonFungiblePostConditionWire;
expect(deserialized.conditionType).toBe(postConditionType);
expect(deserialized.principal.prefix).toBe(PostConditionPrincipalId.Contract);
if (!('address' in deserialized.principal)) throw TypeError;
expect(addressToString(deserialized.principal.address)).toBe(address);
expect((deserialized.principal as ContractPrincipalWire).contractName.content).toBe(contractName);
expect(deserialized.conditionCode).toBe(conditionCode);
Expand Down Expand Up @@ -143,6 +148,7 @@ test('Non-fungible post condition with string IDs serialization and deserializat
) as NonFungiblePostConditionWire;
expect(deserialized.conditionType).toBe(postConditionType);
expect(deserialized.principal.prefix).toBe(PostConditionPrincipalId.Contract);
if (!('address' in deserialized.principal)) throw TypeError;
expect(addressToString(deserialized.principal.address)).toBe(address);
expect((deserialized.principal as ContractPrincipalWire).contractName.content).toBe(contractName);
expect(deserialized.conditionCode).toBe(conditionCode);
Expand Down Expand Up @@ -192,3 +198,30 @@ describe(postConditionToHex.name, () => {
expect(hex).toBe(expected);
});
});

describe('origin postcondition', () => {
test('origin postcondition to wire', () => {
const pc = {
type: 'stx-postcondition',
address: 'origin',
condition: 'eq',
amount: '10000',
} as const;
const wire = postConditionToWire(pc);
expect(wire.conditionType).toBe(PostConditionType.STX);
expect(wire.principal.prefix).toBe(PostConditionPrincipalId.Origin);
});

test('deserialize test vector from stacks-core', () => {
// this same hex, deserialized in the stacks rust lib:
// StacksTransaction { version: Testnet, chain_id: 2147483648, auth: Standard(Singlesig(SinglesigSpendingCondition { hash_mode: P2PKH, signer: a5180cc1ff6050df53f0ab766d76b630e14feb0c, nonce: 7, tx_fee: 2059, key_encoding: Compressed, signature: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 })), anchor_mode: Any, post_condition_mode: Deny, post_conditions: [STX(Origin, SentGe, 1)], payload: ContractCall(TransactionContractCall { address: StacksAddress { version: 26, bytes: 0000000000000000000000000000000000000000 }, contract_name: ContractName("bns"), function_name: ClarityName("name-preorder"), function_args: [Sequence(Buffer(2931e7d082bd215fff3d447d8e2adc7c88d7e207)), UInt(10)] }) }
const txHex =
'0x80800000000400a5180cc1ff6050df53f0ab766d76b630e14feb0c0000000000000007000000000000080b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000302000000010001030000000000000001021a000000000000000000000000000000000000000003626e730d6e616d652d7072656f726465720000000202000000142931e7d082bd215fff3d447d8e2adc7c88d7e207010000000000000000000000000000000a';

expect(() => {
const tx = deserializeTransaction(txHex);
const pc = tx.postConditions.values[0] as PostConditionWire;
expect(pc.principal.prefix).toBe(PostConditionPrincipalId.Origin);
}).not.toThrow();
});
});
2 changes: 2 additions & 0 deletions packages/transactions/tests/transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ test('STX token transfer transaction serialization and deserialization', () => {
expect(deserialized.postConditions.values.length).toBe(1);

const deserializedPostCondition = deserialized.postConditions.values[0] as STXPostConditionWire;
if (!('address' in deserializedPostCondition.principal)) throw TypeError;
expect(deserializedPostCondition.principal.address).toStrictEqual(recipient.address);
expect(deserializedPostCondition.conditionCode).toBe(FungibleConditionCode.GreaterEqual);
expect(deserializedPostCondition.amount.toString()).toBe('0');
Expand Down Expand Up @@ -190,6 +191,7 @@ test('STX token transfer transaction fee setting', () => {

const deserializedPostCondition = postSetFeeDeserialized.postConditions
.values[0] as STXPostConditionWire;
if (!('address' in deserializedPostCondition.principal)) throw TypeError;
expect(deserializedPostCondition.principal.address).toStrictEqual(recipient.address);
expect(deserializedPostCondition.conditionCode).toBe(FungibleConditionCode.GreaterEqual);
expect(deserializedPostCondition.amount.toString()).toBe('0');
Expand Down

0 comments on commit e7a1ddc

Please sign in to comment.