diff --git a/modules/sdk-coin-stx/src/lib/constants.ts b/modules/sdk-coin-stx/src/lib/constants.ts index e9723a2a0f..6542b32e2b 100644 --- a/modules/sdk-coin-stx/src/lib/constants.ts +++ b/modules/sdk-coin-stx/src/lib/constants.ts @@ -1,6 +1,10 @@ export const FUNCTION_NAME_SENDMANY = 'send-many'; export const CONTRACT_NAME_SENDMANY = 'send-many-memo'; -export const CONTRACT_NAME_STAKING = 'pox-3'; +export const CONTRACT_NAME_STAKING = 'pox-4'; + +// TODO: remove support to this contract version after STX fork +// https://bitgoinc.atlassian.net/browse/EA-3482 +export const BACKWARD_COMPATIBILITY_CONTRACT_NAME_STAKING = 'pox-3'; export const VALID_CONTRACT_FUNCTION_NAMES = [ 'stack-stx', diff --git a/modules/sdk-coin-stx/src/lib/contractBuilder.ts b/modules/sdk-coin-stx/src/lib/contractBuilder.ts index c0fab733ad..fe86cf4b1b 100644 --- a/modules/sdk-coin-stx/src/lib/contractBuilder.ts +++ b/modules/sdk-coin-stx/src/lib/contractBuilder.ts @@ -15,7 +15,11 @@ import { Transaction } from './transaction'; import { isValidAddress } from './utils'; import { ClarityValueJson } from './iface'; import { Utils } from '.'; -import { CONTRACT_NAME_SENDMANY, CONTRACT_NAME_STAKING } from './constants'; +import { + BACKWARD_COMPATIBILITY_CONTRACT_NAME_STAKING, + CONTRACT_NAME_SENDMANY, + CONTRACT_NAME_STAKING, +} from './constants'; import { AbstractContractBuilder } from './abstractContractBuilder'; export class ContractBuilder extends AbstractContractBuilder { @@ -60,8 +64,14 @@ export class ContractBuilder extends AbstractContractBuilder { if (name.length === 0) { throw new InvalidParameterValueError('Invalid name'); } - if (name !== CONTRACT_NAME_STAKING && name !== CONTRACT_NAME_SENDMANY) { - throw new InvalidParameterValueError('Only pox-3 and send-many-memo contracts supported'); + if ( + // TODO: remove support to this contract version after STX fork + // https://bitgoinc.atlassian.net/browse/EA-3482 + name !== BACKWARD_COMPATIBILITY_CONTRACT_NAME_STAKING && + name !== CONTRACT_NAME_STAKING && + name !== CONTRACT_NAME_SENDMANY + ) { + throw new InvalidParameterValueError('Only pox-3, pox-4 and send-many-memo contracts supported'); } this._contractName = name; return this; diff --git a/modules/sdk-coin-stx/test/fixtures.ts b/modules/sdk-coin-stx/test/fixtures.ts index 0a240d40e7..3d68bc8f38 100644 --- a/modules/sdk-coin-stx/test/fixtures.ts +++ b/modules/sdk-coin-stx/test/fixtures.ts @@ -10,13 +10,13 @@ export const txExplainedTransfer = { }; export const txForExplainContract = - '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b4000037980c4fe5607724d9d6cbd320d38cea5931cc940ac23b6e36316cf5f077a0ec260e7c5d3916d0e375d4c68e94e4779ad8b7226c34c71fd453b818066a5a30ac030200000000021a000000000000000000000000000000000000000005706f782d3309737461636b2d737478000000040100000000000000000000000017d7840005163248e7aa6879968d241f3e5152d9f2796994d96c090a0c00000002096861736862797465730200000009736f6d652d686173680776657273696f6e020000000101'; + '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b40000dba4d775d6894746f0b8ff3cbc1b0f2696890d0669ffbb8eb8df51098741115e28af448a59583e3d34e344c34879c5c7a82d223833c1214d13cd9813165d8e1e030200000000021a000000000000000000000000000000000000000005706f782d3409737461636b2d737478000000040100000000000000000000000017d7840005163248e7aa6879968d241f3e5152d9f2796994d96c090a0c00000002096861736862797465730200000009736f6d652d686173680776657273696f6e020000000101'; export const txExplainedContract = { - id: '686864ede927cc05a16a842951d96e6e4a201432f33ab01d58e952ef0e958832', + id: '14e44db3f77b527d7a9a0fb2d09c079a7a50c03e06e6a4508a4be417346d810d', fee: '180', contractAddress: 'ST000000000000000000002AMW42H', - contractName: 'pox-3', + contractName: 'pox-4', functionName: 'stack-stx', functionArgs: [{ type: 1, value: '400000000' }], }; diff --git a/modules/sdk-coin-stx/test/unit/resources.ts b/modules/sdk-coin-stx/test/unit/resources.ts index 14075a5400..4d2ebf8330 100644 --- a/modules/sdk-coin-stx/test/unit/resources.ts +++ b/modules/sdk-coin-stx/test/unit/resources.ts @@ -125,20 +125,25 @@ export const MULTI_SIG_SIGNED_TRANSACTION = // contract call export const CONTRACT_ADDRESS = 'ST000000000000000000002AMW42H'; -export const CONTRACT_NAME = 'pox-3'; +export const CONTRACT_NAME = 'pox-4'; export const CONTRACT_FUNCTION_NAME = 'stack-stx'; export const UNSIGNED_SELF_STACK_CONTRACT_CALL = - '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030200000000021a000000000000000000000000000000000000000005706f782d3309737461636b2d737478000000040100000000000000000000000017d784000c00000002096861736862797465730200000009736f6d652d686173680776657273696f6e020000000101010000000000000000000000000000ce400100000000000000000000000000000002'; + '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030200000000021a000000000000000000000000000000000000000005706f782d3409737461636b2d737478000000080100000000000000000000000017d784000c00000002096861736862797465730200000009736f6d652d686173680776657273696f6e020000000101010000000000000000000000000000ce4001000000000000000000000000000000020a0200000009736f6d652d686173680200000009736f6d652d6861736801ffffffffffffffffffffffffffffffff010000000000000000000000000001e240'; export const SIGNED_SELF_STACK_CONTRACT_CALL = - '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b40001710f7ede3c8a199c5303fe42f3b2e984db28b60fd288257773287f595c8142ba43a2dc3c60cf332c9f123ceffd24a26e0fe8d08a1fd36bc3cba118a4c436f397030200000000021a000000000000000000000000000000000000000005706f782d3309737461636b2d737478000000040100000000000000000000000017d784000c00000002096861736862797465730200000009736f6d652d686173680776657273696f6e020000000101010000000000000000000000000000ce400100000000000000000000000000000002'; + '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b40001833db79cd9c39c7262b808b6ea80a001a9bd290648cf3a19da95f10d1814d69437a00075e8325ef564ed9d6690c575583bcbb29c4e33c8ce80c34e7efd1f5457030200000000021a000000000000000000000000000000000000000005706f782d3409737461636b2d737478000000080100000000000000000000000017d784000c00000002096861736862797465730200000009736f6d652d686173680776657273696f6e020000000101010000000000000000000000000000ce4001000000000000000000000000000000020a0200000009736f6d652d686173680200000009736f6d652d6861736801ffffffffffffffffffffffffffffffff010000000000000000000000000001e240'; + +// TODO: remove support to this contract version after STX fork +// https://bitgoinc.atlassian.net/browse/EA-3482 +export const POX_3_SIGNED_SELF_STACK_CONTRACT_CALL = + '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b400002a0f4802bba8d6c1e6311b55a47500046e59be18dc9dc0166fb358d05a20f0cd20f970859601f9f7a558483cc2cf22abd7dab2f5bc05df60093e7ad04a04c6d8030200000000021a000000000000000000000000000000000000000005706f782d3409737461636b2d737478000000040100000000000000000000000017d784000c00000002096861736862797465730200000009736f6d652d686173680776657273696f6e020000000101010000000000000000000000000000ce400100000000000000000000000000000002'; export const UNSIGNED_CONTRACT_CALL = - '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030200000000021a000000000000000000000000000000000000000005706f782d3309737461636b2d737478000000040100000000000000000000000017d7840005163248e7aa6879968d241f3e5152d9f2796994d96c0a01000000000000000000000000000000c80a0c00000002096861736862797465730200000009736f6d652d686173680776657273696f6e020000000101'; + '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030200000000021a000000000000000000000000000000000000000005706f782d3409737461636b2d737478000000040100000000000000000000000017d7840005163248e7aa6879968d241f3e5152d9f2796994d96c0a01000000000000000000000000000000c80a0c00000002096861736862797465730200000009736f6d652d686173680776657273696f6e020000000101'; export const SIGNED_CONTRACT_WITH_ARGS = - '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b4000037980c4fe5607724d9d6cbd320d38cea5931cc940ac23b6e36316cf5f077a0ec260e7c5d3916d0e375d4c68e94e4779ad8b7226c34c71fd453b818066a5a30ac030200000000021a000000000000000000000000000000000000000005706f782d3309737461636b2d737478000000040100000000000000000000000017d7840005163248e7aa6879968d241f3e5152d9f2796994d96c090a0c00000002096861736862797465730200000009736f6d652d686173680776657273696f6e020000000101'; + '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b40000dba4d775d6894746f0b8ff3cbc1b0f2696890d0669ffbb8eb8df51098741115e28af448a59583e3d34e344c34879c5c7a82d223833c1214d13cd9813165d8e1e030200000000021a000000000000000000000000000000000000000005706f782d3409737461636b2d737478000000040100000000000000000000000017d7840005163248e7aa6879968d241f3e5152d9f2796994d96c090a0c00000002096861736862797465730200000009736f6d652d686173680776657273696f6e020000000101'; export const SIGNED_CONTRACT_CALL = - '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b40001d140984651d7b3339c85df78cc59c176237579421112da4eb45ce75803d87176155d48c63afd595a3469b0b2b2a9f81e4806d7ce9b9acdc59d4af23356d37b84030200000000021a000000000000000000000000000000000000000005706f782d3309737461636b2d737478000000010a000000000000000000000000000000007b'; + '80800000000400164247d6f2b425ac5771423ae6c80c754f7172b0000000000000000000000000000000b40001b693bdf014c1ac78e307fed49bc1df8fed2a5e42d4b79993f5df626d7b1b2516741495198a3dbf079deebc46ecdd535fe66f518c0a695c533f1667455a05036a030200000000021a000000000000000000000000000000000000000005706f782d3409737461636b2d737478000000010a000000000000000000000000000000007b'; export const MULTI_SIG_CONTRACT_CALL = - '808000000004012fe507c09dbb23c3b7e5d166c81fc4b87692510b000000000000000000000000000000b400000003020037aed42ad2597983a6803c8122d9bca4c6f3566fc078fe2bbfdcf0d5f7eae68121e575eb94704c0c6cb40a5b1e7a8458bba45ffd1a54408c8efa4bcf0c0a818f02011478d5f323ea1da203fc2661aeb363f66e5b19e4e9d1ed80de99054372d573cd0942fad4e9f5cfba3e8086bb75f5def6c2220afb1feae11f249f334304831f7b00038e3c4529395611be9abf6fa3b6987e81d402385e3d605a073f42f407565a4a3d0002030200000000021a000000000000000000000000000000000000000005706f782d3309737461636b2d737478000000010a000000000000000000000000000000007b'; + '808000000004012fe507c09dbb23c3b7e5d166c81fc4b87692510b000000000000000000000000000000b400000003020169a032112373b481c48ecb12b8847519f89720d4297a7bf5cac43b9daa8d4ad23dd53657089dad55c94e8be5762e97052683f49e1f457b2e9252f27e975603230200128d0ba2b9584339359ff42c1d6f35ce79a7073152b3c90879bc5162eb8df73b02eada3ee322873a5fa736479e6c536109681482d9f22f1e1a89608090b8666800038e3c4529395611be9abf6fa3b6987e81d402385e3d605a073f42f407565a4a3d0002030200000000021a000000000000000000000000000000000000000005706f782d3409737461636b2d737478000000010a000000000000000000000000000000007b'; // contract call with memo nonce 45n, export const SEND_MANY_CONTRACT_ADDRESS_WITH_MEMO = 'ST3F1X4QGV2SM8XD96X45M6RTQXKA1PZJZZCQAB4B'; diff --git a/modules/sdk-coin-stx/test/unit/transactionBuilder/contractBuilder.ts b/modules/sdk-coin-stx/test/unit/transactionBuilder/contractBuilder.ts index cfc4896bda..8bb3625d1a 100644 --- a/modules/sdk-coin-stx/test/unit/transactionBuilder/contractBuilder.ts +++ b/modules/sdk-coin-stx/test/unit/transactionBuilder/contractBuilder.ts @@ -95,6 +95,16 @@ describe('Stacks: Contract Builder', function () { it('an unsigned self stacking contract call transaction', async () => { const builder = initTxBuilder(); + /* Contract call in clarity POX-4 + (define-public (stack-stx (amount-ustx uint) + (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + (start-burn-ht uint) + (lock-period uint) + (signer-sig (optional (buff 65))) + (signer-key (buff 33)) + (max-amount uint) + (auth-id uint)) + */ builder.functionArgs([ { type: 'uint128', val: '400000000' }, { @@ -106,6 +116,12 @@ describe('Stacks: Contract Builder', function () { }, { type: 'uint128', val: '52800' }, { type: 'uint128', val: '2' }, + // Nakamoto upgrade new 4 parameters + // https://docs.stacks.co/nakamoto-upgrade/signing-and-stacking/stacking-flow#solo-stacker-flow + { type: 'optional', val: { type: 'buffer', val: Buffer.from('some-hash') } }, + { type: 'buffer', val: Buffer.from('some-hash') }, + { type: 'uint128', val: '340282366920938463463374607431768211455' }, + { type: 'uint128', val: '123456' }, ]); builder.fromPubKey(testData.TX_SENDER.pub); builder.numberSignatures(1); @@ -167,6 +183,16 @@ describe('Stacks: Contract Builder', function () { it('a signed self stacking contract call', async () => { const builder = initTxBuilder(); + /* Contract call in clarity POX-4 + (define-public (stack-stx (amount-ustx uint) + (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + (start-burn-ht uint) + (lock-period uint) + (signer-sig (optional (buff 65))) + (signer-key (buff 33)) + (max-amount uint) + (auth-id uint)) + */ builder.functionArgs([ { type: 'uint128', val: '400000000' }, { @@ -178,6 +204,12 @@ describe('Stacks: Contract Builder', function () { }, { type: 'uint128', val: '52800' }, { type: 'uint128', val: '2' }, + // Nakamoto upgrade new 4 parameters + // https://docs.stacks.co/nakamoto-upgrade/signing-and-stacking/stacking-flow#solo-stacker-flow + { type: 'optional', val: { type: 'buffer', val: Buffer.from('some-hash') } }, + { type: 'buffer', val: Buffer.from('some-hash') }, + { type: 'uint128', val: '340282366920938463463374607431768211455' }, + { type: 'uint128', val: '123456' }, ]); builder.sign({ key: testData.TX_SENDER.prv }); const tx = await builder.build(); @@ -199,6 +231,42 @@ describe('Stacks: Contract Builder', function () { tx.inputs[0].value.should.equal('0'); }); + // TODO: remove support to this contract version after STX fork + // https://bitgoinc.atlassian.net/browse/EA-3482 + it('a signed pox-3 backward support pre-fork self stacking contract call', async () => { + const builder = initTxBuilder(); + builder.functionArgs([ + { type: 'uint128', val: '400000000' }, + { + type: 'tuple', + val: [ + { key: 'hashbytes', type: 'buffer', val: Buffer.from('some-hash') }, + { key: 'version', type: 'buffer', val: new BigNum(1).toBuffer() }, + ], + }, + { type: 'uint128', val: '52800' }, + { type: 'uint128', val: '2' }, + ]); + builder.sign({ key: testData.TX_SENDER.prv }); + const tx = await builder.build(); + + const txJson = tx.toJson(); + should.deepEqual(txJson.payload.contractAddress, testData.CONTRACT_ADDRESS); + should.deepEqual(txJson.payload.contractName, testData.CONTRACT_NAME); + should.deepEqual(txJson.payload.functionName, testData.CONTRACT_FUNCTION_NAME); + should.deepEqual(txJson.nonce, 0); + should.deepEqual(txJson.fee.toString(), '180'); + should.deepEqual(tx.toBroadcastFormat(), testData.POX_3_SIGNED_SELF_STACK_CONTRACT_CALL); + + tx.type.should.equal(TransactionType.ContractCall); + tx.outputs.length.should.equal(1); + tx.outputs[0].address.should.equal(testData.CONTRACT_ADDRESS); + tx.outputs[0].value.should.equal('0'); + tx.inputs.length.should.equal(1); + tx.inputs[0].address.should.equal(testData.TX_SENDER.address); + tx.inputs[0].value.should.equal('0'); + }); + it('a signed contract call transaction', async () => { const amount = 123; const builder = initTxBuilder(); @@ -246,7 +314,9 @@ describe('Stacks: Contract Builder', function () { should.deepEqual(txJson.payload.functionName, testData.CONTRACT_FUNCTION_NAME); should.deepEqual(txJson.nonce, 0); should.deepEqual(txJson.fee.toString(), '180'); - should.deepEqual(txJson.payload.functionArgs.length, 4); + // Now stacks-stx self-stacking supports 8 parameters + // https://docs.stacks.co/nakamoto-upgrade/signing-and-stacking/stacking-flow#solo-stacker-flow + should.deepEqual(txJson.payload.functionArgs.length, 8); should.deepEqual(tx.toBroadcastFormat(), testData.SIGNED_SELF_STACK_CONTRACT_CALL); tx.type.should.equal(TransactionType.ContractCall); tx.outputs.length.should.equal(1); @@ -257,6 +327,29 @@ describe('Stacks: Contract Builder', function () { tx.inputs[0].value.should.equal('0'); }); + // TODO: remove support to this contract version after STX fork + // https://bitgoinc.atlassian.net/browse/EA-3482 + it('a signed serialized pox-3 backward support pre-fork self stacking contract call transaction', async () => { + const builder = factory.from(testData.POX_3_SIGNED_SELF_STACK_CONTRACT_CALL); + const tx = await builder.build(); + const txJson = tx.toJson(); + should.deepEqual(txJson.payload.contractAddress, testData.CONTRACT_ADDRESS); + should.deepEqual(txJson.payload.contractName, testData.CONTRACT_NAME); + should.deepEqual(txJson.payload.functionName, testData.CONTRACT_FUNCTION_NAME); + should.deepEqual(txJson.nonce, 0); + should.deepEqual(txJson.fee.toString(), '180'); + // POX-3 stacks-stx self-stacking supports 4 parameters + should.deepEqual(txJson.payload.functionArgs.length, 4); + should.deepEqual(tx.toBroadcastFormat(), testData.POX_3_SIGNED_SELF_STACK_CONTRACT_CALL); + tx.type.should.equal(TransactionType.ContractCall); + tx.outputs.length.should.equal(1); + tx.outputs[0].address.should.equal(testData.CONTRACT_ADDRESS); + tx.outputs[0].value.should.equal('0'); + tx.inputs.length.should.equal(1); + tx.inputs[0].address.should.equal(testData.TX_SENDER.address); + tx.inputs[0].value.should.equal('0'); + }); + it('a multisig transfer transaction', async () => { const builder = initTxBuilder(); builder.functionArgs([{ type: 'optional', val: { type: 'int128', val: '123' } }]); @@ -374,7 +467,10 @@ describe('Stacks: Contract Builder', function () { }); it('a contract call with an invalid contract name', () => { const builder = initTxBuilder(); - assert.throws(() => builder.contractName('pox-2'), /Only pox-3 and send-many-memo contracts supported/); + assert.throws( + () => builder.contractName('pox-2'), + /Only pox-3, pox-4 and send-many-memo contracts supported/ + ); }); it('a contract call with an invalid contract function name', () => { const builder = initTxBuilder(); diff --git a/modules/sdk-core/src/bitgo/staking/iStakingWallet.ts b/modules/sdk-core/src/bitgo/staking/iStakingWallet.ts index 8430e88fbd..f7d96e2cef 100644 --- a/modules/sdk-core/src/bitgo/staking/iStakingWallet.ts +++ b/modules/sdk-core/src/bitgo/staking/iStakingWallet.ts @@ -32,7 +32,13 @@ export interface DelegationRequest { * @property {string} [duration] - delegation duration: a numeric string, in days or cycles * @property {string} [subType] - coin sepcific staking subtype * @property {string} [btcRewardAddress] - btc reward address + * @property {string} [signerPub] - stx signer public key + * @property {string} [signerSignature] - stx signer signature * @property {DelegationRequest[]} [delegationRequests] - The delegation requests + * TODO: remove support to this contract version after STX fork + * https://bitgoinc.atlassian.net/browse/EA-3482 + * @property {string} [contractName] - stx contract name: valid names are pox-3 and pox-4 only, used only for backward compatibility during nakamoto fork + */ export interface StakeOptions { amount?: string; @@ -51,14 +57,32 @@ export interface StakeOptions { */ blsSignature?: string; /** - * coin sepcific staking subtype + * coin specific staking subtype */ subType?: string; /** - * btc reward address + * stx btc reward address */ btcRewardAddress?: string; + + /** + * stx signer pub + */ + signerPub?: string; + + /** + * stx signer signature + */ + signerSignature?: string; + delegationRequests?: DelegationRequest[]; + + // TODO: remove support to this contract version after STX fork + // https://bitgoinc.atlassian.net/browse/EA-3482 + /** + * pox-contract name (valid values are pox-3 and pox-4) + */ + contractName?: 'pox-3' | 'pox-4'; } export interface UnstakeOptions {