Skip to content

Commit

Permalink
feat: support versioned-smart-contract tx types introduced in Stack…
Browse files Browse the repository at this point in the history
…s 2.1 (#1341)
  • Loading branch information
zone117x authored and janniks committed Nov 23, 2022
1 parent cdd32e9 commit 0062a45
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 30 deletions.
14 changes: 12 additions & 2 deletions packages/transactions/src/builders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
TxRejectedReason,
RECOVERABLE_ECDSA_SIG_LENGTH_BYTES,
StacksMessageType,
ClarityVersion,
} from './constants';
import { ClarityAbi, validateContractCall } from './contract-abi';
import {
Expand Down Expand Up @@ -689,6 +690,7 @@ export async function makeSTXTokenTransfer(
* Contract deploy transaction options
*/
export interface BaseContractDeployOptions {
clarityVersion?: ClarityVersion;
contractName: string;
/** the Clarity code to be deployed */
codeBody: string;
Expand Down Expand Up @@ -734,7 +736,10 @@ export async function estimateContractDeploy(
transaction: StacksTransaction,
network?: StacksNetworkName | StacksNetwork
): Promise<bigint> {
if (transaction.payload.payloadType !== PayloadType.SmartContract) {
if (
transaction.payload.payloadType !== PayloadType.SmartContract &&
transaction.payload.payloadType !== PayloadType.VersionedSmartContract
) {
throw new Error(
`Contract deploy fee estimation only possible with ${
PayloadType[PayloadType.SmartContract]
Expand Down Expand Up @@ -808,7 +813,11 @@ export async function makeUnsignedContractDeploy(

const options = Object.assign(defaultOptions, txOptions);

const payload = createSmartContractPayload(options.contractName, options.codeBody);
const payload = createSmartContractPayload(
options.contractName,
options.codeBody,
options.clarityVersion
);

const addressHashMode = AddressHashMode.SerializeP2PKH;
const pubKey = createStacksPublicKey(options.publicKey);
Expand Down Expand Up @@ -1384,6 +1393,7 @@ export async function sponsorTransaction(
switch (options.transaction.payload.payloadType) {
case PayloadType.TokenTransfer:
case PayloadType.SmartContract:
case PayloadType.VersionedSmartContract:
case PayloadType.ContractCall:
const estimatedLen = estimateTransactionByteLength(options.transaction);
try {
Expand Down
7 changes: 7 additions & 0 deletions packages/transactions/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,17 @@ export function whenMessageType(messageType: StacksMessageType) {
enum PayloadType {
TokenTransfer = 0x00,
SmartContract = 0x01,
VersionedSmartContract = 0x06,
ContractCall = 0x02,
PoisonMicroblock = 0x03,
Coinbase = 0x04,
}

enum ClarityVersion {
Clarity1 = 1,
Clarity2 = 2,
}

/**
* How a transaction should get appended to the Stacks blockchain.
*
Expand Down Expand Up @@ -178,6 +184,7 @@ export {
ChainID,
StacksMessageType,
PayloadType,
ClarityVersion,
AnchorMode,
TransactionVersion,
PostConditionMode,
Expand Down
1 change: 1 addition & 0 deletions packages/transactions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export {
TokenTransferPayload,
ContractCallPayload,
SmartContractPayload,
VersionedSmartContractPayload,
PoisonPayload,
CoinbasePayload,
serializePayload,
Expand Down
70 changes: 52 additions & 18 deletions packages/transactions/src/payload.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import { concatArray, IntegerType, intToBigInt, intToBytes, writeUInt32BE } from '@stacks/common';
import { COINBASE_BYTES_LENGTH, PayloadType, StacksMessageType } from './constants';
import { ClarityVersion, COINBASE_BYTES_LENGTH, PayloadType, StacksMessageType } from './constants';

import { BytesReader } from './bytesReader';
import { ClarityValue, deserializeCV, serializeCV } from './clarity/';
import { PrincipalCV, principalCV } from './clarity/types/principalCV';
import { Address } from './common';
import { createAddress, createLPString, LengthPrefixedString } from './postcondition-types';
import {
MemoString,
codeBodyString,
createMemoString,
serializeStacksMessage,
deserializeAddress,
deserializeLPString,
deserializeMemoString,
codeBodyString,
MemoString,
serializeStacksMessage,
} from './types';
import { createAddress, LengthPrefixedString, createLPString } from './postcondition-types';
import { Address } from './common';
import { ClarityValue, serializeCV, deserializeCV } from './clarity/';

import { BytesReader } from './bytesReader';
import { PrincipalCV, principalCV } from './clarity/types/principalCV';

export type Payload =
| TokenTransferPayload
| ContractCallPayload
| SmartContractPayload
| VersionedSmartContractPayload
| PoisonPayload
| CoinbasePayload;

Expand Down Expand Up @@ -52,6 +52,7 @@ export type PayloadInput =
| (TokenTransferPayload | (Omit<TokenTransferPayload, 'amount'> & { amount: IntegerType }))
| ContractCallPayload
| SmartContractPayload
| VersionedSmartContractPayload
| PoisonPayload
| CoinbasePayload;

Expand Down Expand Up @@ -118,23 +119,42 @@ export interface SmartContractPayload {
readonly codeBody: LengthPrefixedString;
}

export interface VersionedSmartContractPayload {
readonly type: StacksMessageType.Payload;
readonly payloadType: PayloadType.VersionedSmartContract;
readonly clarityVersion: ClarityVersion;
readonly contractName: LengthPrefixedString;
readonly codeBody: LengthPrefixedString;
}

export function createSmartContractPayload(
contractName: string | LengthPrefixedString,
codeBody: string | LengthPrefixedString
): SmartContractPayload {
codeBody: string | LengthPrefixedString,
clarityVersion?: ClarityVersion
): SmartContractPayload | VersionedSmartContractPayload {
if (typeof contractName === 'string') {
contractName = createLPString(contractName);
}
if (typeof codeBody === 'string') {
codeBody = codeBodyString(codeBody);
}

return {
type: StacksMessageType.Payload,
payloadType: PayloadType.SmartContract,
contractName,
codeBody,
};
if (typeof clarityVersion === 'number') {
return {
type: StacksMessageType.Payload,
payloadType: PayloadType.VersionedSmartContract,
clarityVersion: clarityVersion,
contractName,
codeBody,
};
} else {
return {
type: StacksMessageType.Payload,
payloadType: PayloadType.SmartContract,
contractName,
codeBody,
};
}
}

export interface PoisonPayload {
Expand Down Expand Up @@ -188,6 +208,11 @@ export function serializePayload(payload: PayloadInput): Uint8Array {
bytesArray.push(serializeStacksMessage(payload.contractName));
bytesArray.push(serializeStacksMessage(payload.codeBody));
break;
case PayloadType.VersionedSmartContract:
bytesArray.push(payload.clarityVersion);
bytesArray.push(serializeStacksMessage(payload.contractName));
bytesArray.push(serializeStacksMessage(payload.codeBody));
break;
case PayloadType.PoisonMicroblock:
// TODO: implement
break;
Expand Down Expand Up @@ -230,6 +255,15 @@ export function deserializePayload(bytesReader: BytesReader): Payload {
const smartContractName = deserializeLPString(bytesReader);
const codeBody = deserializeLPString(bytesReader, 4, 100_000);
return createSmartContractPayload(smartContractName, codeBody);

case PayloadType.VersionedSmartContract: {
const clarityVersion = bytesReader.readUInt8Enum(ClarityVersion, n => {
throw new Error(`Cannot recognize ClarityVersion: ${n}`);
});
const smartContractName = deserializeLPString(bytesReader);
const codeBody = deserializeLPString(bytesReader, 4, 100_000);
return createSmartContractPayload(smartContractName, codeBody, clarityVersion);
}
case PayloadType.PoisonMicroblock:
// TODO: implement
return createPoisonPayload();
Expand Down
1 change: 1 addition & 0 deletions packages/transactions/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export class StacksTransaction {
}
case PayloadType.ContractCall:
case PayloadType.SmartContract:
case PayloadType.VersionedSmartContract:
case PayloadType.TokenTransfer: {
this.anchorMode = AnchorMode.Any;
break;
Expand Down
27 changes: 27 additions & 0 deletions packages/transactions/tests/builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
AddressHashMode,
AnchorMode,
AuthType,
ClarityVersion,
DEFAULT_CORE_NODE_API_URL,
FungibleConditionCode,
NonFungibleConditionCode,
Expand Down Expand Up @@ -656,6 +657,32 @@ test('addSignature to an unsigned transaction', async () => {
expect(unsignedTx).not.toBe(signedTx);
});

test('Make versioned smart contract deploy', async () => {
const contractName = 'kv-store';
const codeBody = fs.readFileSync('./tests/contracts/kv-store.clar').toString();
const senderKey = 'e494f188c2d35887531ba474c433b1e41fadd8eb824aca983447fd4bb8b277a801';
const fee = 0;
const nonce = 0;

const transaction = await makeContractDeploy({
contractName,
codeBody,
senderKey,
fee,
nonce,
network: new StacksTestnet(),
anchorMode: AnchorMode.Any,
clarityVersion: ClarityVersion.Clarity2,
});

const serialized = bytesToHex(transaction.serialize());

const tx =
'80800000000400e6c05355e0c990ffad19a5e9bda394a9c50034290000000000000000000000000000000000009172c9841e763c32e827c177491f5228956e6ef1071043be898bfdd694bf3e680309b0666e8fec013a8a453573a8bd707152c9f21aa6f2d5e57c407af672b6f00302000000000602086b762d73746f72650000015628646566696e652d6d61702073746f72652028286b657920286275666620333229292920282876616c7565202862756666203332292929290a0a28646566696e652d7075626c696320286765742d76616c756520286b65792028627566662033322929290a20202020286d6174636820286d61702d6765743f2073746f72652028286b6579206b65792929290a2020202020202020656e74727920286f6b20286765742076616c756520656e74727929290a20202020202020202865727220302929290a0a28646566696e652d7075626c696320287365742d76616c756520286b65792028627566662033322929202876616c75652028627566662033322929290a2020202028626567696e0a2020202020202020286d61702d7365742073746f72652028286b6579206b6579292920282876616c75652076616c75652929290a2020202020202020286f6b2027747275652929290a';

expect(serialized).toBe(tx);
});

test('Make smart contract deploy', async () => {
const contractName = 'kv-store';
const codeBody = fs.readFileSync('./tests/contracts/kv-store.clar').toString();
Expand Down
43 changes: 33 additions & 10 deletions packages/transactions/tests/payload.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import {
TokenTransferPayload,
ContractCallPayload,
SmartContractPayload,
CoinbasePayload,
createSmartContractPayload,
ContractCallPayload,
createCoinbasePayload,
createContractCallPayload,
createSmartContractPayload,
createTokenTransferPayload,
SmartContractPayload,
TokenTransferPayload,
VersionedSmartContractPayload,
} from '../src/payload';

import { serializeDeserialize } from './macros';

import { trueCV, falseCV, standardPrincipalCV, contractPrincipalCV } from '../src/clarity';
import { contractPrincipalCV, falseCV, standardPrincipalCV, trueCV } from '../src/clarity';

import { COINBASE_BYTES_LENGTH, StacksMessageType } from '../src/constants';
import { bytesToUtf8, utf8ToBytes } from '@stacks/common';
import { principalToString } from '../src/clarity/types/principalCV';
import { bytesToUtf8 } from '@stacks/common';
import { ClarityVersion, StacksMessageType } from '../src/constants';

test('STX token transfer payload serialization and deserialization', () => {
const recipient = standardPrincipalCV('SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159');
Expand Down Expand Up @@ -118,10 +119,32 @@ test('Smart contract payload serialization and deserialization', () => {
expect(deserialized.codeBody.content).toBe(codeBody);
});

test('Versioned smart contract payload serialization and deserialization', () => {
const contractName = 'contract_name';
const codeBody =
'(define-map store ((key (buff 32))) ((value (buff 32))))' +
'(define-public (get-value (key (buff 32)))' +
' (match (map-get? store ((key key)))' +
' entry (ok (get value entry))' +
' (err 0)))' +
'(define-public (set-value (key (buff 32)) (value (buff 32)))' +
' (begin' +
' (map-set store ((key key)) ((value value)))' +
" (ok 'true)))";

const payload = createSmartContractPayload(contractName, codeBody, ClarityVersion.Clarity2);

const deserialized = serializeDeserialize(
payload,
StacksMessageType.Payload
) as VersionedSmartContractPayload;
expect(deserialized.clarityVersion).toBe(2);
expect(deserialized.contractName.content).toBe(contractName);
expect(deserialized.codeBody.content).toBe(codeBody);
});

test('Coinbase payload serialization and deserialization', () => {
// eslint-disable-next-line node/prefer-global/buffer
const coinbaseBuffer = Buffer.alloc(COINBASE_BYTES_LENGTH, 0);
coinbaseBuffer.write('coinbase buffer');
const coinbaseBuffer = utf8ToBytes('coinbase buffer ');

const payload = createCoinbasePayload(coinbaseBuffer);

Expand Down

0 comments on commit 0062a45

Please sign in to comment.