mirror of
https://github.com/alexgo-io/stacks-blockchain-api.git
synced 2026-04-29 05:15:32 +08:00
feat: support for subnets (#1625)
* feat: beta release with subnets support * chore(release): 7.1.0-beta.1 [skip ci] ## [7.1.0-beta.1](https://github.com/hirosystems/stacks-blockchain-api/compare/v7.0.0...v7.1.0-beta.1) (2023-02-14) ### Features * beta release with subnets support ([06164eb](06164eb1dd)) * support for subnets ([#1549](https://github.com/hirosystems/stacks-blockchain-api/issues/1549)) ([5d7056c](5d7056c1ba)) ### Bug Fixes * fixed the order of microblocks_streamed returned in reverse order in block endpoint ([#1528](https://github.com/hirosystems/stacks-blockchain-api/issues/1528)) ([764f64a](764f64a538)) * Merge master into beta * chore: update l1 and l2 subnet contracts * chore: update subnets docker compose to latest 2.1 image * chore: enable subnet STX transfer tests * chore: progress on fixing test for subnet to L1 FT withdrawal * feat: support register asset event synthetic tx parsing (#1583) * feat: support register asset event synthetic tx parsing * test: integration tests for register-new-ft-contract and register-new-nft-contract synthetic txs * feat: update 'register-asset-contract' event parsing * chore: bump to latest subnet docker image (also include subnets.Dockerfile for local dev) * chore(release): 7.1.0-beta.2 [skip ci] ## [7.1.0-beta.2](https://github.com/hirosystems/stacks-blockchain-api/compare/v7.1.0-beta.1...v7.1.0-beta.2) (2023-03-16) ### Features * support register asset event synthetic tx parsing ([#1583](https://github.com/hirosystems/stacks-blockchain-api/issues/1583)) ([57d58f2](57d58f2f8d)) --------- Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
version: '3.7'
|
||||
services:
|
||||
stacks-blockchain:
|
||||
image: "hirosystems/stacks-api-e2e:stacks2.1-ecb1872"
|
||||
image: "hirosystems/stacks-api-e2e:stacks2.1-a50d830"
|
||||
command: |
|
||||
bash -c "rm /event-log.ndjson && /root/run.sh"
|
||||
ports:
|
||||
@@ -19,8 +19,9 @@ services:
|
||||
- "host.docker.internal:host-gateway" # fixes `host.docker.internal` on linux hosts
|
||||
stacks-subnet:
|
||||
# restart: on-failure
|
||||
#image: "hirosystems/stacks-subnets:206-merge-stretch"
|
||||
image: "hirosystems/stacks-subnets@sha256:7e296cd319f81b87b5a9c11f4c478dafb122c114dc805d832d68f39be150af49"
|
||||
image: "hirosystems/stacks-subnets:7012d22"
|
||||
# build:
|
||||
# dockerfile: ./subnet-node.Dockerfile
|
||||
command: subnet-node start --config=/app/config/Stacks-subnet.toml
|
||||
ports:
|
||||
- "30443:30443" # subnet-node RPC interface
|
||||
|
||||
25
docker/subnet-node.Dockerfile
Normal file
25
docker/subnet-node.Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
FROM rust:bullseye as build
|
||||
|
||||
ARG STACKS_NODE_VERSION="No Version Info"
|
||||
ARG GIT_BRANCH='No Branch Info'
|
||||
ARG GIT_COMMIT='No Commit Info'
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
RUN git clone https://github.com/hirosystems/stacks-subnets.git .
|
||||
RUN git checkout 77c6625947cdf66ab02acc5c03c08e5142911494
|
||||
|
||||
RUN mkdir /out /contracts
|
||||
|
||||
RUN cd testnet/stacks-node && cargo build --features monitoring_prom,slog_json --release
|
||||
|
||||
RUN cp target/release/subnet-node /out
|
||||
|
||||
FROM debian:bullseye-backports
|
||||
|
||||
COPY --from=build /out/ /bin/
|
||||
# Add the core contracts to the image, so that clarinet can retrieve them.
|
||||
COPY --from=build /src/core-contracts/contracts/subnet.clar /contracts/subnet.clar
|
||||
COPY --from=build /src/core-contracts/contracts/helper/subnet-traits.clar /contracts/subnet-traits.clar
|
||||
|
||||
CMD ["subnet-node", "start"]
|
||||
@@ -148,6 +148,28 @@ interface FtBurnEvent extends CoreNodeEventBase {
|
||||
};
|
||||
}
|
||||
|
||||
interface BurnchainOpRegisterAssetNft {
|
||||
register_asset: {
|
||||
asset_type: 'nft';
|
||||
burn_header_hash: string;
|
||||
l1_contract_id: string;
|
||||
l2_contract_id: string;
|
||||
txid: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface BurnchainOpRegisterAssetFt {
|
||||
register_asset: {
|
||||
asset_type: 'ft';
|
||||
burn_header_hash: string;
|
||||
l1_contract_id: string;
|
||||
l2_contract_id: string;
|
||||
txid: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type BurnchainOp = BurnchainOpRegisterAssetNft | BurnchainOpRegisterAssetFt;
|
||||
|
||||
export type CoreNodeEvent =
|
||||
| SmartContractEvent
|
||||
| StxTransferEvent
|
||||
@@ -174,6 +196,7 @@ export interface CoreNodeTxMessage {
|
||||
microblock_sequence: number | null;
|
||||
microblock_hash: string | null;
|
||||
microblock_parent_hash: string | null;
|
||||
burnchain_op?: BurnchainOp | null;
|
||||
}
|
||||
|
||||
export interface CoreNodeMicroblockTxMessage extends CoreNodeTxMessage {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
BurnchainOp,
|
||||
CoreNodeBlockMessage,
|
||||
CoreNodeEvent,
|
||||
CoreNodeEventType,
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
TxPublicKeyEncoding,
|
||||
TxSpendingConditionSingleSigHashMode,
|
||||
decodeClarityValueList,
|
||||
ClarityValueBuffer,
|
||||
} from 'stacks-encoding-native-js';
|
||||
import {
|
||||
DbMicroblockPartial,
|
||||
@@ -45,6 +47,7 @@ import {
|
||||
I32_MAX,
|
||||
bufferToHexPrefixString,
|
||||
hexToBuffer,
|
||||
SubnetContractIdentifer,
|
||||
} from '../helpers';
|
||||
import {
|
||||
TransactionVersion,
|
||||
@@ -83,6 +86,89 @@ export function getTxSponsorAddress(tx: DecodedTxResult): string | undefined {
|
||||
return sponsorAddress;
|
||||
}
|
||||
|
||||
function createSubnetTransactionFromL1RegisterAsset(
|
||||
chainId: ChainID,
|
||||
burnchainOp: BurnchainOp,
|
||||
subnetEvent: SmartContractEvent,
|
||||
txId: string
|
||||
): DecodedTxResult {
|
||||
if (
|
||||
burnchainOp.register_asset.asset_type !== 'ft' &&
|
||||
burnchainOp.register_asset.asset_type !== 'nft'
|
||||
) {
|
||||
throw new Error(
|
||||
`Unexpected L1 register asset type: ${JSON.stringify(burnchainOp.register_asset)}`
|
||||
);
|
||||
}
|
||||
|
||||
const [contractAddress, contractName] = subnetEvent.contract_event.contract_identifier
|
||||
.split('::')[0]
|
||||
.split('.');
|
||||
const decContractAddress = decodeStacksAddress(contractAddress);
|
||||
|
||||
const decodedLogEvent = decodeClarityValue<
|
||||
ClarityValueTuple<{
|
||||
'burnchain-txid': ClarityValueBuffer;
|
||||
}>
|
||||
>(subnetEvent.contract_event.raw_value);
|
||||
|
||||
// (define-public (register-asset-contract
|
||||
// (asset-type (string-ascii 3))
|
||||
// (l1-contract principal)
|
||||
// (l2-contract principal)
|
||||
// (burnchain-txid (buff 32))
|
||||
const fnName = 'register-asset-contract';
|
||||
const legacyClarityVals = [
|
||||
stringAsciiCV(burnchainOp.register_asset.asset_type),
|
||||
principalCV(burnchainOp.register_asset.l1_contract_id),
|
||||
principalCV(burnchainOp.register_asset.l2_contract_id),
|
||||
bufferCV(hexToBuffer(decodedLogEvent.data['burnchain-txid'].buffer)),
|
||||
];
|
||||
const fnLenBuffer = Buffer.alloc(4);
|
||||
fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
|
||||
const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
|
||||
const rawFnArgs = bufferToHexPrefixString(
|
||||
Buffer.concat([fnLenBuffer, ...serializedClarityValues])
|
||||
);
|
||||
const clarityFnArgs = decodeClarityValueList(rawFnArgs);
|
||||
|
||||
const tx: DecodedTxResult = {
|
||||
tx_id: txId,
|
||||
version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
|
||||
chain_id: chainId,
|
||||
auth: {
|
||||
type_id: PostConditionAuthFlag.Standard,
|
||||
origin_condition: {
|
||||
hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
|
||||
signer: {
|
||||
address_version: decContractAddress[0],
|
||||
address_hash_bytes: decContractAddress[1],
|
||||
address: contractAddress,
|
||||
},
|
||||
nonce: '0',
|
||||
tx_fee: '0',
|
||||
key_encoding: TxPublicKeyEncoding.Compressed,
|
||||
signature: '0x',
|
||||
},
|
||||
},
|
||||
anchor_mode: AnchorModeID.Any,
|
||||
post_condition_mode: PostConditionModeID.Allow,
|
||||
post_conditions: [],
|
||||
post_conditions_buffer: '0x0100000000',
|
||||
payload: {
|
||||
type_id: TxPayloadTypeID.ContractCall,
|
||||
address_version: decContractAddress[0],
|
||||
address_hash_bytes: decContractAddress[1],
|
||||
address: contractAddress,
|
||||
contract_name: contractName,
|
||||
function_name: fnName,
|
||||
function_args: clarityFnArgs,
|
||||
function_args_buffer: rawFnArgs,
|
||||
},
|
||||
};
|
||||
return tx;
|
||||
}
|
||||
|
||||
function createSubnetTransactionFromL1NftDeposit(
|
||||
chainId: ChainID,
|
||||
event: NftMintEvent,
|
||||
@@ -573,6 +659,14 @@ export function parseMessageTransaction(
|
||||
})
|
||||
.find(e => !!e);
|
||||
|
||||
const subnetEvents = events.filter(
|
||||
(e): e is SmartContractEvent =>
|
||||
e.type === CoreNodeEventType.ContractEvent &&
|
||||
e.contract_event.topic === 'print' &&
|
||||
(e.contract_event.contract_identifier === SubnetContractIdentifer.mainnet ||
|
||||
e.contract_event.contract_identifier === SubnetContractIdentifer.testnet)
|
||||
);
|
||||
|
||||
if (stxTransferEvent) {
|
||||
rawTx = createTransactionFromCoreBtcTxEvent(chainId, stxTransferEvent, coreTx.txid);
|
||||
txSender = stxTransferEvent.stx_transfer_event.sender;
|
||||
@@ -608,6 +702,18 @@ export function parseMessageTransaction(
|
||||
} else if (stxMintEvent) {
|
||||
rawTx = createSubnetTransactionFromL1StxDeposit(chainId, stxMintEvent, coreTx.txid);
|
||||
txSender = getTxSenderAddress(rawTx);
|
||||
} else if (
|
||||
subnetEvents.length > 0 &&
|
||||
coreTx.burnchain_op &&
|
||||
coreTx.burnchain_op.register_asset
|
||||
) {
|
||||
rawTx = createSubnetTransactionFromL1RegisterAsset(
|
||||
chainId,
|
||||
coreTx.burnchain_op,
|
||||
subnetEvents[0],
|
||||
coreTx.txid
|
||||
);
|
||||
txSender = getTxSenderAddress(rawTx);
|
||||
} else {
|
||||
logError(
|
||||
`BTC transaction found, but no STX transfer event available to recreate transaction. TX: ${JSON.stringify(
|
||||
|
||||
@@ -1074,6 +1074,11 @@ export function getBnsSmartContractId(chainId: ChainID): string {
|
||||
: 'ST000000000000000000002AMW42H.bns::names';
|
||||
}
|
||||
|
||||
export const enum SubnetContractIdentifer {
|
||||
mainnet = 'SP000000000000000000002Q6VF78.subnet',
|
||||
testnet = 'ST000000000000000000002AMW42H.subnet',
|
||||
}
|
||||
|
||||
export function getSendManyContract(chainId: ChainID) {
|
||||
const contractId =
|
||||
chainId === ChainID.Mainnet
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
;; https://github.com/hirosystems/stacks-subnets/blob/master/core-contracts/contracts/helper/simple-ft.clar
|
||||
|
||||
(define-constant ERR_NOT_AUTHORIZED (err u1001))
|
||||
|
||||
(impl-trait 'STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP.sip-traits.ft-trait)
|
||||
@@ -7,8 +9,7 @@
|
||||
|
||||
;; get the token balance of owner
|
||||
(define-read-only (get-balance (owner principal))
|
||||
(begin
|
||||
(ok (ft-get-balance ft-token owner))))
|
||||
(ok (ft-get-balance ft-token owner)))
|
||||
|
||||
;; returns the total number of tokens
|
||||
(define-read-only (get-total-supply)
|
||||
@@ -49,9 +50,9 @@
|
||||
(define-read-only (get-token-uri)
|
||||
(ok none))
|
||||
|
||||
(define-public (gift-tokens (recipient principal))
|
||||
(define-public (gift-tokens (amount uint) (recipient principal))
|
||||
(begin
|
||||
(asserts! (is-eq tx-sender recipient) ERR_NOT_AUTHORIZED)
|
||||
(ft-mint? ft-token u1 recipient)
|
||||
(ft-mint? ft-token amount recipient)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
;; https://github.com/hirosystems/stacks-subnets/blob/master/core-contracts/contracts/helper/simple-nft.clar
|
||||
|
||||
(define-constant CONTRACT_OWNER tx-sender)
|
||||
(define-constant CONTRACT_ADDRESS (as-contract tx-sender))
|
||||
|
||||
(define-constant ERR_NOT_AUTHORIZED (err u1001))
|
||||
|
||||
(impl-trait 'STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP.nft-trait.nft-trait)
|
||||
(impl-trait 'STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP.sip-traits.nft-trait)
|
||||
(impl-trait 'STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP.subnet-traits.mint-from-subnet-trait)
|
||||
|
||||
(define-data-var lastId uint u0)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
;; https://github.com/hirosystems/stacks-subnets/blob/master/core-contracts/contracts/helper/sip-traits.clar
|
||||
|
||||
(define-trait nft-trait
|
||||
(
|
||||
;; Last token ID, limited to uint range
|
||||
@@ -37,4 +39,4 @@
|
||||
;; an optional URI that represents metadata of this token
|
||||
(get-token-uri () (response (optional (string-utf8 256)) uint))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
;; https://github.com/hirosystems/stacks-subnets/blob/master/core-contracts/contracts/helper/subnet-traits.clar
|
||||
|
||||
;; In order to process deposits and withdrawals to a subnet, an asset
|
||||
;; contract must implement this trait.
|
||||
(define-trait mint-from-subnet-trait
|
||||
@@ -13,4 +15,4 @@
|
||||
(response bool uint)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
;; https://github.com/hirosystems/stacks-subnets/blob/master/core-contracts/contracts/subnet.clar
|
||||
|
||||
;; The .subnet contract
|
||||
|
||||
(define-constant CONTRACT_ADDRESS (as-contract tx-sender))
|
||||
@@ -31,8 +33,10 @@
|
||||
;; Map recording processed withdrawal leaves
|
||||
(define-map processed-withdrawal-leaves-map { withdrawal-leaf-hash: (buff 32), withdrawal-root-hash: (buff 32) } bool)
|
||||
|
||||
;; List of miners
|
||||
;; principal that can commit blocks
|
||||
(define-data-var miner principal 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6)
|
||||
;; principal that can register contracts
|
||||
(define-data-var admin principal 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6)
|
||||
|
||||
;; Map of allowed contracts for asset transfers - maps L1 contract principal to L2 contract principal
|
||||
(define-map allowed-contracts principal principal)
|
||||
@@ -53,13 +57,20 @@
|
||||
;; Register a new FT contract to be supported by this subnet.
|
||||
(define-public (register-new-ft-contract (ft-contract <ft-trait>) (l2-contract principal))
|
||||
(begin
|
||||
;; Verify that tx-sender is an authorized miner
|
||||
(asserts! (is-miner tx-sender) (err ERR_INVALID_MINER))
|
||||
;; Verify that tx-sender is an authorized admin
|
||||
(asserts! (is-admin tx-sender) (err ERR_INVALID_MINER))
|
||||
|
||||
;; Set up the assets that the contract is allowed to transfer
|
||||
(asserts! (map-insert allowed-contracts (contract-of ft-contract) l2-contract)
|
||||
(err ERR_ASSET_ALREADY_ALLOWED))
|
||||
|
||||
(print {
|
||||
event: "register-contract",
|
||||
asset-type: "ft",
|
||||
l1-contract: (contract-of ft-contract),
|
||||
l2-contract: l2-contract
|
||||
})
|
||||
|
||||
(ok true)
|
||||
)
|
||||
)
|
||||
@@ -67,13 +78,20 @@
|
||||
;; Register a new NFT contract to be supported by this subnet.
|
||||
(define-public (register-new-nft-contract (nft-contract <nft-trait>) (l2-contract principal))
|
||||
(begin
|
||||
;; Verify that tx-sender is an authorized miner
|
||||
(asserts! (is-miner tx-sender) (err ERR_INVALID_MINER))
|
||||
;; Verify that tx-sender is an authorized admin
|
||||
(asserts! (is-admin tx-sender) (err ERR_INVALID_MINER))
|
||||
|
||||
;; Set up the assets that the contract is allowed to transfer
|
||||
(asserts! (map-insert allowed-contracts (contract-of nft-contract) l2-contract)
|
||||
(err ERR_ASSET_ALREADY_ALLOWED))
|
||||
|
||||
(print {
|
||||
event: "register-contract",
|
||||
asset-type: "nft",
|
||||
l1-contract: (contract-of nft-contract),
|
||||
l2-contract: l2-contract
|
||||
})
|
||||
|
||||
(ok true)
|
||||
)
|
||||
)
|
||||
@@ -84,6 +102,12 @@
|
||||
(is-eq miner-to-check (var-get miner))
|
||||
)
|
||||
|
||||
;; Helper function: returns a boolean indicating whether the given principal is an admin
|
||||
;; Returns bool
|
||||
(define-private (is-admin (addr-to-check principal))
|
||||
(is-eq addr-to-check (var-get admin))
|
||||
)
|
||||
|
||||
;; Helper function: determines whether the commit-block operation satisfies pre-conditions
|
||||
;; listed in `commit-block`.
|
||||
;; Returns response<bool, int>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
;; https://github.com/hirosystems/stacks-subnets/blob/master/core-contracts/contracts/helper/simple-ft-l2.clar
|
||||
|
||||
(define-constant ERR_NOT_AUTHORIZED (err u1001))
|
||||
|
||||
(impl-trait 'ST000000000000000000002AMW42H.subnet.ft-trait)
|
||||
@@ -6,8 +8,7 @@
|
||||
|
||||
;; get the token balance of owner
|
||||
(define-read-only (get-balance (owner principal))
|
||||
(begin
|
||||
(ok (ft-get-balance ft-token owner))))
|
||||
(ok (ft-get-balance ft-token owner)))
|
||||
|
||||
;; returns the total number of tokens
|
||||
(define-read-only (get-total-supply)
|
||||
@@ -38,8 +39,11 @@
|
||||
(ok none)
|
||||
)
|
||||
|
||||
(define-read-only (get-token-balance (user principal))
|
||||
(ft-get-balance ft-token user)
|
||||
(define-public (gift-tokens (amount uint) (recipient principal))
|
||||
(begin
|
||||
(asserts! (is-eq tx-sender recipient) ERR_NOT_AUTHORIZED)
|
||||
(ft-mint? ft-token amount recipient)
|
||||
)
|
||||
)
|
||||
|
||||
(impl-trait 'ST000000000000000000002AMW42H.subnet.subnet-asset)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
;; https://github.com/hirosystems/stacks-subnets/blob/master/core-contracts/contracts/helper/simple-nft-l2.clar
|
||||
|
||||
(define-constant CONTRACT_OWNER tx-sender)
|
||||
(define-constant CONTRACT_ADDRESS (as-contract tx-sender))
|
||||
|
||||
|
||||
@@ -272,6 +272,96 @@ describe('Subnets tests', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('Step 2b: Validate register-asset-contract synthetic tx', async () => {
|
||||
while (true) {
|
||||
const expectedContractID = `ST000000000000000000002AMW42H.subnet`;
|
||||
const resp = await supertest(testEnv.api.server)
|
||||
.get(`/extended/v1/tx?limit=1&type=contract_call`)
|
||||
.expect(200);
|
||||
const txListResp = resp.body as TransactionResults;
|
||||
const tx = txListResp.results[0] as ContractCallTransaction;
|
||||
if (
|
||||
txListResp.total === 0 ||
|
||||
tx.contract_call.contract_id !== expectedContractID ||
|
||||
tx.contract_call.function_name !== 'register-asset-contract'
|
||||
) {
|
||||
await timeout(200);
|
||||
continue;
|
||||
}
|
||||
expect(tx).toEqual(
|
||||
expect.objectContaining({
|
||||
anchor_mode: 'any',
|
||||
canonical: true,
|
||||
contract_call: {
|
||||
contract_id: expectedContractID,
|
||||
function_args: [
|
||||
expect.objectContaining({
|
||||
name: 'asset-type',
|
||||
repr: '"nft"',
|
||||
type: '(string-ascii 3)',
|
||||
}),
|
||||
{
|
||||
hex: '0x061a43596b5386f466863e25658ddf94bd0fadab00480d73696d706c652d6e66742d6c31',
|
||||
name: 'l1-contract',
|
||||
repr: `'${accounts.USER.addr}.simple-nft-l1`,
|
||||
type: 'principal',
|
||||
},
|
||||
{
|
||||
hex: '0x061a43596b5386f466863e25658ddf94bd0fadab00480d73696d706c652d6e66742d6c32',
|
||||
name: 'l2-contract',
|
||||
repr: `'${accounts.USER.addr}.simple-nft-l2`,
|
||||
type: 'principal',
|
||||
},
|
||||
expect.objectContaining({
|
||||
name: 'burnchain-txid',
|
||||
type: '(buff 32)',
|
||||
}),
|
||||
],
|
||||
function_name: 'register-asset-contract',
|
||||
function_signature:
|
||||
'(define-public (register-asset-contract (asset-type (string-ascii 3)) (l1-contract principal) (l2-contract principal) (burnchain-txid (buff 32))))',
|
||||
},
|
||||
event_count: 1,
|
||||
events: [],
|
||||
fee_rate: '0',
|
||||
post_condition_mode: 'allow',
|
||||
post_conditions: [],
|
||||
sender_address: 'ST000000000000000000002AMW42H',
|
||||
sponsored: false,
|
||||
tx_index: 0,
|
||||
tx_result: {
|
||||
hex: '0x0703',
|
||||
repr: '(ok true)',
|
||||
},
|
||||
tx_status: 'success',
|
||||
tx_type: 'contract_call',
|
||||
})
|
||||
);
|
||||
|
||||
const respEvents = await supertest(testEnv.api.server)
|
||||
.get(`/extended/v1/tx/events?tx_id=${tx.tx_id}`)
|
||||
.expect(200);
|
||||
const txEvents = respEvents.body.events as TransactionEventsResponse['results'];
|
||||
expect(txEvents).toEqual([
|
||||
{
|
||||
contract_log: {
|
||||
contract_id: 'ST000000000000000000002AMW42H.subnet',
|
||||
topic: 'print',
|
||||
value: expect.objectContaining({
|
||||
repr: expect.stringContaining(
|
||||
`(l1-contract '${accounts.USER.addr}.simple-nft-l1) (l2-contract '${accounts.USER.addr}.simple-nft-l2)`
|
||||
),
|
||||
}),
|
||||
},
|
||||
event_index: 0,
|
||||
event_type: 'smart_contract_log',
|
||||
tx_id: tx.tx_id,
|
||||
},
|
||||
]);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
test('Step 3: Mint an NFT on the L1 chain', async () => {
|
||||
const tx = await makeContractCall({
|
||||
contractAddress: accounts.USER.addr,
|
||||
@@ -617,12 +707,100 @@ describe('Subnets tests', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('Step 2b: Validate register-asset-contract synthetic tx', async () => {
|
||||
while (true) {
|
||||
const expectedContractID = `ST000000000000000000002AMW42H.subnet`;
|
||||
const resp = await supertest(testEnv.api.server)
|
||||
.get(`/extended/v1/tx?limit=1&type=contract_call`)
|
||||
.expect(200);
|
||||
const txListResp = resp.body as TransactionResults;
|
||||
const tx = txListResp.results[0] as ContractCallTransaction;
|
||||
if (
|
||||
txListResp.total === 0 ||
|
||||
tx.contract_call.contract_id !== expectedContractID ||
|
||||
tx.contract_call.function_name !== 'register-asset-contract'
|
||||
) {
|
||||
await timeout(200);
|
||||
continue;
|
||||
}
|
||||
expect(tx).toEqual(
|
||||
expect.objectContaining({
|
||||
anchor_mode: 'any',
|
||||
canonical: true,
|
||||
contract_call: {
|
||||
contract_id: expectedContractID,
|
||||
function_args: [
|
||||
expect.objectContaining({
|
||||
name: 'asset-type',
|
||||
repr: '"ft"',
|
||||
type: '(string-ascii 3)',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'l1-contract',
|
||||
repr: `'${accounts.USER.addr}.simple-ft-l1`,
|
||||
type: 'principal',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'l2-contract',
|
||||
repr: `'${accounts.USER.addr}.simple-ft-l2`,
|
||||
type: 'principal',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'burnchain-txid',
|
||||
type: '(buff 32)',
|
||||
}),
|
||||
],
|
||||
function_name: 'register-asset-contract',
|
||||
function_signature:
|
||||
'(define-public (register-asset-contract (asset-type (string-ascii 3)) (l1-contract principal) (l2-contract principal) (burnchain-txid (buff 32))))',
|
||||
},
|
||||
event_count: 1,
|
||||
events: [],
|
||||
fee_rate: '0',
|
||||
post_condition_mode: 'allow',
|
||||
post_conditions: [],
|
||||
sender_address: 'ST000000000000000000002AMW42H',
|
||||
sponsored: false,
|
||||
tx_index: 0,
|
||||
tx_result: {
|
||||
hex: '0x0703',
|
||||
repr: '(ok true)',
|
||||
},
|
||||
tx_status: 'success',
|
||||
tx_type: 'contract_call',
|
||||
})
|
||||
);
|
||||
|
||||
const respEvents = await supertest(testEnv.api.server)
|
||||
.get(`/extended/v1/tx/events?tx_id=${tx.tx_id}`)
|
||||
.expect(200);
|
||||
const txEvents = respEvents.body.events as TransactionEventsResponse['results'];
|
||||
expect(txEvents).toEqual([
|
||||
{
|
||||
contract_log: {
|
||||
contract_id: 'ST000000000000000000002AMW42H.subnet',
|
||||
topic: 'print',
|
||||
value: expect.objectContaining({
|
||||
repr: expect.stringContaining(
|
||||
`(l1-contract '${accounts.USER.addr}.simple-ft-l1) (l2-contract '${accounts.USER.addr}.simple-ft-l2)`
|
||||
),
|
||||
}),
|
||||
},
|
||||
event_index: 0,
|
||||
event_type: 'smart_contract_log',
|
||||
tx_id: tx.tx_id,
|
||||
},
|
||||
]);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
test('Step 3: Mint FT on the L1 chain', async () => {
|
||||
const tx = await makeContractCall({
|
||||
contractAddress: accounts.USER.addr,
|
||||
contractName: 'simple-ft-l1',
|
||||
functionName: 'gift-tokens',
|
||||
functionArgs: [standardPrincipalCV(accounts.USER.addr)],
|
||||
functionArgs: [uintCV(1), standardPrincipalCV(accounts.USER.addr)],
|
||||
senderKey: accounts.USER.key,
|
||||
validateWithAbi: false,
|
||||
network: l1Network,
|
||||
@@ -804,7 +982,7 @@ describe('Subnets tests', () => {
|
||||
withdrawal_root: string;
|
||||
sibling_hashes: string;
|
||||
}>(
|
||||
`v2/withdrawal/ft/${withdrawalBlockHeight}/${accounts.ALT_USER.addr}/${withdrawalId}/${accounts.USER.addr}/simple-ft-l2/5`
|
||||
`v2/withdrawal/ft/${withdrawalBlockHeight}/${accounts.ALT_USER.addr}/${withdrawalId}/${accounts.USER.addr}/simple-ft-l2/1`
|
||||
);
|
||||
console.log(json_merkle_entry);
|
||||
const cv_merkle_entry = {
|
||||
@@ -843,8 +1021,8 @@ describe('Subnets tests', () => {
|
||||
const result = await callReadOnlyFunction({
|
||||
contractAddress: accounts.USER.addr,
|
||||
contractName: 'simple-ft-l1',
|
||||
functionName: 'get-owner',
|
||||
functionArgs: [uintCV(5)],
|
||||
functionName: 'get-balance',
|
||||
functionArgs: [standardPrincipalCV(accounts.ALT_USER.addr)],
|
||||
network: l1Network,
|
||||
senderAddress: accounts.ALT_USER.addr,
|
||||
});
|
||||
@@ -860,7 +1038,7 @@ describe('Subnets tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('STX use-case test', () => {
|
||||
describe('STX use-case test', () => {
|
||||
test('Step 1: Publish STX contract to L2', async () => {
|
||||
const curBlock = await l2Client.getInfo();
|
||||
await standByUntilBlock(curBlock.stacks_tip_height + 1);
|
||||
|
||||
Reference in New Issue
Block a user