feat: add deploy smart contract debug API endpoint

This commit is contained in:
Matthew Little
2020-03-30 21:33:27 +02:00
parent d3863ef8e0
commit 0199ebb5f6
6 changed files with 90 additions and 3 deletions

5
package-lock.json generated
View File

@@ -1955,6 +1955,11 @@
"once": "^1.4.0"
}
},
"escape-goat": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz",
"integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw=="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",

View File

@@ -38,6 +38,7 @@
"compression": "^1.7.4",
"cross-env": "^7.0.2",
"dotenv": "^8.2.0",
"escape-goat": "^3.0.0",
"express": "^4.17.1",
"node-pg-migrate": "^4.2.3",
"pg": "^7.18.2",

View File

@@ -3,10 +3,12 @@ import * as path from 'path';
import * as express from 'express';
import * as BN from 'bn.js';
import { addAsync } from '@awaitjs/express';
import { makeSTXTokenTransfer, TransactionVersion, Address } from '@blockstack/stacks-transactions/src';
import { htmlEscape } from 'escape-goat';
import { makeSTXTokenTransfer, TransactionVersion, makeSmartContractDeploy } from '@blockstack/stacks-transactions/src';
import { BufferReader } from '../../binary-reader';
import { readTransaction } from '../../p2p/tx';
import { txidFromData } from '@blockstack/stacks-transactions/src/utils';
import * as BroadcastContractDefault from '../../sample-data/broadcast-contract-default.json';
const testnetKeys: { secretKey: string; publicKey: string; stacksAddress: string }[] = [
{
@@ -103,6 +105,66 @@ export function createDebugRouter(): express.Router {
.send(tokenTransferHtml + '<h3>Broadcasted transaction:</h3>' + `<a href="/tx/${txId}">${txId}</a>`);
});
const contractDeployHtml = `
<style>
input, textarea {
display: block;
width: 100%;
margin-bottom: 10;
}
</style>
<form action="" method="post">
<label for="origin_key">Sender key</label>
<input list="origin_keys" name="origin_key" value="${testnetKeys[0].secretKey}">
<datalist id="origin_keys">
${testnetKeys.map(k => '<option value="' + k.secretKey + '">').join('\n')}
</datalist>
<label for="fee_rate">uSTX tx fee</label>
<input type="number" id="fee_rate" name="fee_rate" value="9">
<label for="nonce">Nonce</label>
<input type="number" id="nonce" name="nonce" value="0">
<label for="contract_name">Contract name</label>
<input type="text" id="contract_name" name="contract_name" value="${htmlEscape(
BroadcastContractDefault.contract_name
)}" pattern="^[a-zA-Z]([a-zA-Z0-9]|[-_!?+&lt;&gt;=/*])*$|^[-+=/*]$|^[&lt;&gt;]=?$" maxlength="128">
<label for="source_code">Contract Clarity source code</label>
<textarea id="source_code" name="source_code" rows="52">${htmlEscape(
BroadcastContractDefault.contract_source
)}</textarea>
<input type="submit" value="Submit">
</form>
`;
router.getAsync('/broadcast/contract-deploy', (req, res) => {
res.set('Content-Type', 'text/html').send(contractDeployHtml);
});
router.postAsync('/broadcast/contract-deploy', (req, res) => {
const { origin_key, contract_name, source_code, fee_rate, nonce } = req.body;
const normalized_contract_source = (source_code as string).replace(/\r/g, '').replace(/\t/g, ' ');
const deployTx = makeSmartContractDeploy(contract_name, normalized_contract_source, new BN(fee_rate), origin_key, {
nonce: new BN(nonce),
version: TransactionVersion.Testnet,
});
const serialized = deployTx.serialize();
const mempoolPath = process.env['STACKS_CORE_MEMPOOL_PATH'];
if (!mempoolPath) {
throw new Error('STACKS_CORE_MEMPOOL_PATH not specified');
}
const txBinPath = path.join(mempoolPath, `tx_${Date.now()}.bin`);
fs.writeFileSync(txBinPath, serialized);
const txId = '0x' + txidFromData(serialized);
res
.set('Content-Type', 'text/html')
.send(contractDeployHtml + '<h3>Broadcasted transaction:</h3>' + `<a href="/tx/${txId}">${txId}</a>`);
});
const txWatchHtml = `
<script>
const sse = new EventSource('/tx/stream?protocol=eventsource');

View File

@@ -6,6 +6,7 @@ import { CoreNodeParsedTxMessage } from '../event-stream/core-node-message';
import { TransactionAuthTypeID, TransactionPayloadTypeID } from '../p2p/tx';
import { c32address } from 'c32check';
import { NotImplementedError } from '../errors';
import { Address } from '@blockstack/stacks-transactions/src';
export interface DbBlock {
block_hash: string;
@@ -197,6 +198,16 @@ export function createDbTxFromCoreMsg(msg: CoreNodeParsedTxMessage): DbTx {
dbTx.token_transfer_memo = rawTx.payload.memo;
break;
}
case TransactionPayloadTypeID.SmartContract: {
const sender_address = Address.fromHashMode(
rawTx.auth.originCondition.hashMode as number,
rawTx.version as number,
rawTx.auth.originCondition.signer.toString('hex')
).toString();
dbTx.smart_contract_contract_id = sender_address + '.' + rawTx.payload.name;
dbTx.smart_contract_source_code = rawTx.payload.codeBody;
break;
}
case TransactionPayloadTypeID.ContractCall: {
const contractAddress = c32address(rawTx.payload.address.version, rawTx.payload.address.bytes.toString('hex'));
dbTx.contract_call_contract_id = `${contractAddress}.${rawTx.payload.contractName}`;

View File

@@ -0,0 +1,5 @@
{
"contract_name": "hello-world-contract",
"contract_source": "(define-constant sender 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR)\n(define-constant recipient 'SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G)\n\n(define-fungible-token novel-token-19)\n(define-public (test-transfer-ft)\n (begin\n (unwrap-panic (ft-transfer? novel-token-19 u10 sender recipient))\n (ok u1)))\n(define-public (test-mint-ft)\n (begin\n (unwrap-panic (ft-mint? novel-token-19 u11 recipient))\n (ok u1)))\n(begin (ft-mint? novel-token-19 u12 sender))\n\n(define-non-fungible-token hello-nft uint)\n(define-public (test-transfer-nft)\n (begin\n (unwrap-panic (nft-transfer? hello-nft u1 sender recipient))\n (ok u1)))\n(define-public (test-mint-nft)\n (begin\n (unwrap-panic (nft-mint? hello-nft u2 recipient))\n (ok u2)))\n(begin (nft-mint? hello-nft u1 sender))\n\n(define-public (test-emit-event)\n (begin\n (print \"Event! Hello world\")\n (ok u1)))\n\n(define-public (test-event-types)\n (begin\n (test-emit-event)\n (test-mint-ft)\n (test-transfer-ft)\n (test-mint-nft)\n (test-transfer-nft)\n (unwrap-panic (stx-transfer? u60 tx-sender 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR))\n (unwrap-panic (stx-burn? u20 tx-sender))\n (ok 'true)))\n\n(define-map store ((key (buff 32))) ((value (buff 32))))\n(define-public (get-value (key (buff 32)))\n (begin\n (match (map-get? store ((key key)))\n entry (ok (get value entry))\n (err 0))))\n(define-public (set-value (key (buff 32)) (value (buff 32)))\n (begin\n (map-set store ((key key)) ((value value)))\n (ok 'true)))"
}

View File

@@ -10,11 +10,14 @@
"sourceRoot": ".",
"esModuleInterop": false,
"allowSyntheticDefaultImports": false,
"resolveJsonModule": true,
},
"include": [
"src/**/*",
"migrations/**/*",
"tests/**/*"
"tests/**/*",
],
"exclude": ["lib"]
"exclude": [
"lib",
]
}