SIP Number: 015
Title: Stacks Upgrade of Proof-of-Transfer and Clarity
Authors: Aaron Blankstein [email protected], Mike Cohen [email protected], Greg Coppola [email protected], Brice Dobry [email protected], Hero Gamer [email protected] Matthew Little [email protected], Jenny Mith [email protected], Jude Nelson [email protected], Pavitthra Pandurangan [email protected], Jason Schrader [email protected], Rena Shah [email protected], Hank Stoever [email protected], Igor Sylvester [email protected], Jesse Wiley [email protected],
Consideration: Technical, Governance, Economics
Type: Consensus
Status: Ratified
Created: 1 December 2021
License: BSD 2-Clause
Sign-off: Brice Dobry [email protected], Jason Schrader [email protected], MattyTokenomics, Jude Nelson [email protected]
Discussions-To: https://github.com/stacksgov/sips
This SIP proposes a set of updates to three major areas of the Stacks blockchain:
-
Stacking, the Proof-of-Transfer (PoX) consensus algorithm as implemented in the Stacks chain and originally proposed in SIP-007. The proposed changes improve the user and developer experience of participating in Stacking, and add support for new behaviors and PoX reward address types.
-
Clarity, the smart contract language supported on the Stacks blockchain. The proposed changes fix bugs and add new native functions and global variables that improve its support for interacting with off-chain services and blockchains, especially Bitcoin.
-
Block Validation, the procedure by which blocks are determined to be acceptable to the Stacks blockchain. The proposed changes address bugs in the implementation that are not specified by any prior SIP, but which cannot be changed without a coordinated network-wide upgrade. In addition, the proposed changes address potential consensus challenges for PoX-related chain reorganizations which were not known at the time SIP-007 was written. Finally, new variations of existing transactions are proposed to better support Stacking, to support multiple Clarity versions, and to support decentralized mining pools.
In addition to proposing these changes, this SIP also outlines an approach for implementing these changes in the Stacks blockchain.
Because this is a breaking change, there must be a vote from the relevant stakeholders to activate this SIP. This vote is slated to take place during reward cycles 46 and 47. This window is estimated to begin starting November 10, 2022 and ending December 8, 2022.
This SIP condenses lessons learned from running the Stacks blockchain in production to date. Since the system was launched in January 2021, several shortcomings have been discovered which can only be addressed through a coordinated, backwards-compatible network upgrade. This SIP enumerates these shortcomings and proposes solutions.
Broadly speaking, the shortcomings of the system fall into one of three categories: Stacking, the Clarity language, and block validation.
Concerning Stacking, the current PoX implementation in the Stacks blockchain has several shortcomings for developers and users:
-
After unlocking, Stackers must wait one cycle before Stacking again. This is an implementation consequence, not a requirement of the algorithm itself.
-
If users fail to qualify during a cycle, their funds remain locked for the cycle regardless.
-
PoX cannot support advanced behaviors that could be used for different kinds of smart contracts: changing the PoX reward address, increasing or decreasing lock amounts, etc. These features would enable new kinds of applications that build on top of PoX.
-
PoX will automatically sunset after a pre-defined number of Bitcoin blocks pass, regardless of how popular or useful it is.
This SIP proposes the creation of a new pox-2
smart contract to implement
Stacking which addresses the above problems. A migration procedure is presented
for transitioning the system from pox
to pox-2
.
At the same time, it was discovered over the lifetime of the Stacks blockchain that there are a few shortcomings in the block validation logic that negatively impact the user experience and miner experience. In particular:
-
Stacking as proposed in SIP-007 has a design flaw: If it is ever the case that a PoX anchor block is missing, and yet somehow manages to achieve 80% or more confirmations during the prepare phase, then the subsequent arrival of that anchor block will cause a deep chain reorganization. It does not matter how many future blocks get mined--- if the anchor block is later revealed, it will invalidate all of the blocks that did not build on it. While mining and confirming a hidden anchor block is very costly, it is possible.
-
An on-burnchain transaction mined in Bitcoin block
N
will only be mined in the Stacks blockchain if Bitcoin blockN+1
selects a Stacks block. Even then, the transaction only materializes in Stacks forks which include this Stacks block. This is brittle in the face of both orphan Stacks blocks (which will never be canonical) and "flash blocks" -- quickly-mined Bitcoin blocks that contain no block-commits. -
The Stacks blockchain calculates the probability of a miner winning a block race as proportional to the minimum of their last block-commit's Bitcoin spend, and the median of their last six block-commits' spends. The last six block-commits today only include block-commits that arrived in their intended Bitcoin blocks. This unfairly punishes miners whose block-commits, through no fault of the miner's, are late to be mined on the Bitcoin chain.
-
Today, there are multiple ways for a Stacks miner to process a transaction but be forced to drop it from the block they are building because its inclusion would invalidate the block. While the current implementation employs various measures to mitigate the impact of this shortcoming, a "proper" solution would be to make it so that this simply never happens -- miners should get paid for all of the work they do, even if the transaction is invalid.
This SIP proposes fixing these four problems. Regarding the first, the SIP proposes organizing PoX anchor blocks into a forkable history, whose canonical fork is determined via Nakamoto consensus. The canonical Stacks fork must contain all canonical PoX anchor blocks. Regarding the second, an on-burnchain transaction will be considered in each Stacks block to be mined in the subsequent six burnchain blocks, instead of the next. Regarding the third, the median Bitcoin spend of the miner's last six block-commits will include late block-commits. Regarding the fourth, the SIP proposes changing a few rules for transaction-processing that would permit the miner to include transactions in blocks that it cannot include today.
Finally, a year of active development of smart contracts on the Stacks blockchain highlighted areas for improved support in the Clarity smart contracting language. These areas include increasing the visibility of burnchain operations (on the Stacks chain, these are Bitcoin operations) and general improvements to the Clarity programming language.
This SIP specification is structured like a changelog. Proposed changes to each of these areas of the system are described in order of new features, changed features, and fixed features. A rationale is provided for each proposed modification to Stacks.
Applying these upgrades to the Stacks blockchain requires a consensus-breaking network upgrade, in this case, a hard fork. Like other such changes, this will require a new Stacks epoch. In this SIP, we will refer to this new epoch as Stacks 2.1.
At the onset of Stacks 2.1, the Clarity VM will begin to support "Clarity Version 2". This version will include support for the new native methods proposed in this SIP (and therefore include new keywords which cannot be used for method names or variable names). New contracts launched in Stacks 2.1 will default to Clarity 2, but contract authors will be able to use a new contract-publish transaction type to indicate if a contract should specifically launch using Clarity 1 or Clarity 2.
Additionally, a new pox-2
contract will be
published by the boot address. The stacks-node
will use the new
pox-2
contract for determining PoX reward sets and governing PoX
locks. Similarly, new rules for block validation take effect at the onset of Stacks
2.1.
This section of the SIP specifies the new and changed behaviors of the PoX-2 contract and also provides new use cases that demonstrate how the new PoX-2 contract could be used.
The new PoX contract operates exclusively with PoX state that was
created in the new contract. It will not "import" state from the
original PoX contract. In order to allow for this, a particular reward
cycle N
is chosen as the "Last PoX-1" reward cycle, and then the
"First PoX-2" reward cycle is the subsequent cycle (N+1
). In-between these
two cycles, there exists a burnchain block height v1_unlock_height
at which
all of the STX tokens in PoX-1 will unlock, so that they can be locked up again
in PoX-2 before cycle N+1
starts.
This defines three periods of PoX operation:
Period 1 | Period 2a | Period 2b | Period 3 |
---|---|---|---|
2.0 Consensus Rules in Effect | 2.1 Consensus Rules enacted, PoX-2 exists, but PoX-1 is still active | PoX-1 deactivates, all tokens locked in the original PoX contract unlock, but first PoX-2 reward cycle has not begun | First PoX-2 reward cycle has begun |
This is the period of time before the 2.1 fork. | This is the period of time after the 2.1 fork, and before v1_unlock_height (inclusive) |
This is the period of time between v1_unlock_height + 1 and the start of cycle (N+1) |
This is the start of cycle (N+1), and all cycles afterward. The original PoX contract state will no longer have any impact on reward sets, account lock status, etc. |
- Every account that is locked by PoX-1 for cycle
N
and beyond is unlocked at the end of period 2a, once the burnchain block heightv1_unlock_height
passes. - Accounts locked in PoX-2 will remain locked. It will be possible to do this with unlocked STX at the start of period 2a.
- Every account that is locked in PoX-2 is eligible for the new calls to PoX-2 described in this document, including those that extend the lock period and those that increase the amount locked.
- Calls to PoX-2 which would attempt to create state for a cycle
before
(N+1)
will fail. Accounts cannot "back-date" their PoX-2 lockups. - Calls to the PoX-1 contract which would attempt to create
state for a cycle
>= N+1
will be made to fail, and any state afterN+1
is ignored. This requires interposing on contract-calls during period 2 and checking the reward cycles arguments: the relevant functions arestack-stx
,delegate-stack-stx
,stack-aggregation-commit
, andreject-pox
.
PoX-2 is available at the start of period 2a. Accounts can begin to lock up their liquid STX into PoX-2 at the start of this period, even though PoX-1 is still active.
Once period 2a finishes, PoX-1 is defunct. It will only be possible to call its read-only functions. Calling any functions that create or modify state will fail. All tokens locked in PoX-1 (but not PoX-2) will unlock at the end of period 2a.
PoX reward payouts continue to happen for cycle N
, even though PoX-1 will be
disabled between cycle N
and N+1
. This is because the reward set is chosen
at the start of the reward cycle, and used to schedule miner payouts over the
course of the cycle. Cycle N
is the last reward cycle in which PoX-1 will be
queried to calculate the reward set. Starting in cycle N+1
, PoX-2 will be
queried for the reward set.
The value for v1_unlock_height
will be determined before this SIP activates.
It will fall as close to the start of cycle N
as possible in order to give
users the maximum amount of time to re-lock their now-unlocked STX into PoX-2,
if they so desire.
This method allows direct stackers to re-lock their funds for up to an additional 12 cycles before their current lock has expired. The target use case for this is to allow users to repeatedly stack without a cooldown phase.
This method checks that the stacker is still allowed to stack, that the stacker has not delegated to an operator, and that their funds are currently stacked. The caller may supply a new reward address for the extension.
The special case handler for the PoX contract in the Clarity VM will check this method's return value and set the stacker's STX account to "auto-unlock" at the end of the last extended-to reward cycle.
This method allows operators to re-lock the funds of one of their delegation participants for additional cycles before that participants lock has expired. The target use case for this is to allow delegation operators to repeatedly stack on behalf of their users without cooldowns.
This method checks that the delegator is still authorized on behalf of the given stacker (and remains authorized until the end of the locked-for period) and that the funds are currently stacked.
Note that, just as with the existing delegate-stack-stx
function,
this method locks but does not commit the user's STX. The delegation
operator must invoke stack-aggregation-commit
to set a reward address
for the locked funds.
The special case handler for the PoX contract in the Clarity VM will check this method's return value and set the stacker's STX account to "auto-unlock" at the end of the last extended-to reward cycle.
This method allows direct stackers to lock additional funds for the remaining cycles on their current lock.
This method checks that the stacker is still allowed to stack, that the stacker has not delegated to an operator, that their funds are currently stacked, and that they have enough unlocked funds to cover the increase. The caller may not supply a new reward address for the increase.
The special case handler for the PoX contract in the Clarity VM will check this method's return value and set the locked amount in the stacker's STX account to correspond to the increased amount.
This method allows operators to lock additional funds for one of their delegation participants for the remaining cycles on that user's current lock.
This method checks that the delegator is still authorized on behalf of
the given stacker (and remains authorized until the end of the
locked-for period), that the increased amount remains less than the
delegation's amount-ustx
field, that the user is currently locked,
and that the user has enough unlocked funds to cover the increase.
Note that, just as with the existing delegate-stack-stx
function,
this method locks but does not commit the user's STX. The delegation
operator must invoke stack-aggregation-commit
to set a reward address
for the newly locked funds.
The special case handler for the PoX contract in the Clarity VM will check this method's return value and set the locked amount in the stacker's STX account to correspond to the increased amount.
This method behaves identically to stack-aggregation-commit
, except that it
returns the index of the PoX address in the reward set calculated by the
pox-2
contract. This index can be used as an argument to the
stack-aggregation-increase
function (see below), which is used to increase the
amount of STX locked for a PoX address by amounts that may be smaller than the
current Stacking minimum.
This method behaves identically to stack-aggregation-commit
, but will permit
the caller to increase the amount of STX locked up for an existing PoX
address. The PoX address is identified by an index returned by the new
stack-aggregation-commit-indexed
method.
Unlike stack-aggregation-commit
, there is no minimum number of STX that
must have been locked to be added to the
existing PoX address. The only requirement is that the total number of STX
exceeds the current Stacking minimum at the time of the call.
This method has been changed so that the user can call it even while their STX
are locked. This is meant to enable the user to increase their STX allowance to
their delegator can lock up for them while their STX are locked. In such cases,
the user would call revoke-delegate-stx
and then delegate-stx
with their
higher STX allowance, and the delegator would subsequently call
delegate-stack-increase
and stack-aggregation-commit
to increase the user's
locked-and-committed STX.
This SIP proposes that if the user's STX do not earn a single reward slot in a reward cycle, then the user's STX are automatically unlocked at the start of the reward cycle. The user's STX would remain unlocked even if they had locked them for subsequent reward cycles.
If a user has amassed more STX, or believes that they can acquire a reward slot in a subsequent cycle, then they can re-stack their STX.
The current system behavior is to keep the user's STX locked for the duration of their specified lock-up period, even if they are not earning them any reward slots.
This SIP proposes removing the automatic PoX sunset that is currently slated to activate in the current system. The rationale for the PoX sunset was to address an incentive problem with PoX: if miners acquire enough STX, they can mine at a discount because the burnchain tokens they pay will be paid back to themselves. While the existence of Stacking pools means this can't be avoided at any scale, it is particularly bad for the chain if miners have so many STX Stacked that they can control the median mining commitment value in the sortition weight calculation. If they can do this, then they can spend as many burnchain tokens as they want since they will get them back right away. The effect of this behavior is that miners who can discount-mine will eventually price out all other miners, leading to a chain where only large STX-holders can effectively mine.
The PoX sunset fixes this incentive problem by capping any gains such a miner could ever make over the system's lifetime. Even if a miner did this, it would lead to a temporary gain in their mining power. However, Stacking has proven successful and popular, and we believe that stopping its operation on an automated schedule has the potential to harm the Stacks blockchain ecosystem.
As an alternative, we recommend that discount-mining behavior be policed by vigilent users. In the system today, 25% of the liquid STX can vote to stop PoX payouts for the next reward cycle. If discount-mining behavior becomes the dominant strategy in Stacks, then users already have the power to fix the miner incentives if it becomes a problem. We do not know when, or if, it ever will, but we believe that the mechanism(s) for determining when or if PoX deactivates to counter discount-mining must be (1) adaptive in the face of an ever-shifting set of discount-mining strategies and the strategies for countering them, and (2) under the control of users, not Stacks blockchain developers. The PoX sunset has neither of these properties.
A future SIP may propose an alternative mechanism for empowering users to collectively address discount-mining in the event that either a better strategy be discovered, the Stacks blockchain incentives get altered to render this impractical.
The type of a PoX address is now (tuple (hashbytes (buff 32)) (version (buff 1)))
. This is to accommodate pay-to-witness-script-hash (p2wsh) and taproot (p2tr) scriptPubKeys on Bitcoin. In
addition, new values for version
are supported to represent these encodings:
0x04
means this is a pay-to-witness-public-key-hash (p2wpkh) address, andhashbytes
is the 20-byte hash160 of the witness script0x05
means this is a pay-to-witness-script-hash (p2wsh) address, andhashbytes
is the 32-byte sha256 of the witness script0x06
means this is a pay-to-taproot (p2tr) address, andhashbytes
is the 32-byte sha256 of the witness script
The pox
contract's implementation of contract-caller allowance
expirations is broken. pox-2
should fix this behavior.
This behavior is not explicitly specified in SIP-007, but is a behavior present
in the reference implementation which enables users to allow other principals
(e.g. smart contracts) to Stack their STX on their behalf. In the pox
contracts, users can set the principal allowed to do this at any time, and
specify the maximum burnchain height for which the authorization will be valid
(once this burnchain height passes, the allowance is automatically revoked).
Direct stackers wishing to repeatedly stack without cooldown may issue a simple contract-call transaction, invoking:
`SP000000000000000000002Q6VF78.pox-2 stack-extend`
If the user wishes to increase the amount that they have stacked in addition to repeating the stacking lockup, they can separately issue a contract-call transaction to:
`SP000000000000000000002Q6VF78.pox-2 stack-increase`
Users can launch a utility smart-contract to execute both of these contract-calls atomically, but they must remember to invoke the contract caller allowance for that utility contract.
Delegation operators wishing to repeatedly stack with a given user without cooldown may do the following:
- Issue a contract-call on behalf of the delegated stacker they wish to re-stack:
(contract-call? SP000000000000000000002Q6VF78.pox-2 delegate-stack-extend
...)
- If the user wishes to increase the amount of STX they have stacked, then the user must first reset their delegated STX allowance:
(contract-call? SP000000000000000000002Q6VF78.pox-2 revoke-delegate-stx
...)
(contract-call? SP000000000000000000002Q6VF78.pox-2 delegate-stx
...)
Then, the delegator must separately issue a delegate-stack-increase
call
which locks but does not commit the increased funds.
-
The aforementioned contract-calls lock but do not commit the user's funds. In order for the delegation operator to register the reward address for those funds, they must invoke the aggregation commit function (just as when they invoke
delegate-stack-stx
).If this is the first time the delegation operator is Stacking STX for a particular PoX reward address, they would call the new
stack-aggregation-commit-indexed
function, and remember its return value:
(contract-call? SP000000000000000000002Q6VF78.pox-2 stack-aggregation-commit-indexed
...)
If this is a subsequent time locking up user STX -- that is, if the delegation operator has already earned a PoX reward address, or did so during this reward cycle, then they can increase the amount of STX locked to it as follows:
(contract-call? SP000000000000000000002Q6VF78.pox-2 stack-aggregation-increase
...)
This permits the delegation operator to lock additional user STX, even if
they do not by themselves exceed the Stacking minimum (a limitation imposed
by the older stack-aggregation-commit
function).
Direct stackers cannot use multiple reward address for the same Stacking address. This is because the direct stacking interface does not support partial stacking operations (i.e., separating the locking operation from the stacking operation), which are necessary for supporting this use case. Users who wish to do this should use the delegation interfaces.
Even in PoX-1, delegation operators are capable of assigning multiple
reward addresses for funds that they manage: each invocation of
delegate-stack-stx
can use a different PoX reward address. However,
if operators wished to split the same account's lockups across
multiple addresses, this would not be possible.
In PoX-2, however, delegation operators can use
delegate-stack-increase
to achieve this. This method allows the
delegation operator to set a new PoX address to receive the
"partially stacked" funds from the increase. Once called, the operator
can use stack-aggregation-commit
to commit to each PoX reward
address separately.
If this SIP is ratified, then at the time of the
Stacks 2.1 network upgrade, the Clarity smart
contract language will be expanded with new features and some new
behaviors for existing native methods. In order to support these
changes, this SIP proposes to introduce Clarity versioning for smart
contracts. In this scheme, each smart contract will be associated
with a particular Clarity version. The execution environment will
track the current version for a given execution (in the
implementation, this will be via the ContractContext
), and use that
to select which features are available, and which native method
implementations will be used. Clarity 2 contracts can invoke Clarity 1
contracts (and vice-versa), but particular care will need to be taken
if a new native keyword is used in the Clarity 1 contract's API.
For example:
Contract A (Clarity 1 Contract):
(define-public (stx-account) ...)
Contract B (Clarity 2 Contract):
(contract-call contract-A stx-account)
In such cases, the new keyword (in the above example, the keyword is
stx-account
) cannot be used by the Clarity 2 contract, and
therefore invoking the public method of Contract A from Contract B is
not allowed. To address these cases, contract authors must launch a
Clarity 1 contract that interposes on Contract A, providing a Clarity
2 compatible interface.
Existing, pre-2.1 contracts will all be Clarity 1. New contracts will default to Clarity 2, and a new Stacks transaction wire format for publishing smart contracts will allow contract publishers to choose the version that their contract should use.
Note that the act of adding, changing, or removing a native Clarity function or native Clarity global variable (including comparators and operators, which are themselves native functions) necessitates the creation of a new version of the Clarity language, and must be treated as a breaking change. This is because adding, changing, or removing either of these things alters the rules for block validation, which makes these consensus-level changes. This SIP proposes introducing a new version of Clarity (Clarity 2) while also preserving the current version of Clarity (Clarity 1). This shall not be construed as setting a precedent -- a future SIP may remove the ability to publish new smart contracts with older versions of Clarity.
- Input Signature:
(stx-account (principal-address principal))
- Output Signature:
{ locked: uint, unlock-height: uint, unlocked: uint }
This method returns the status of STX account: the account's current STX balance, the amount of STX that is currently locked, and the unlock height for the account.
Rationale: This method will be used by the pox-2
contract to validate
various contract-calls: implementing early unlocks, lock extensions,
lock increases, etc. It exposes PoX lock state to smart contracts, which is not
entirely visible even in pox
or pox-2
.
Examples:
(stx-account 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR) ;; Returns (tuple (locked u0) (unlock-height u0) (unlocked u0))
(stx-account (as-contract tx-sender)) ;; Returns (tuple (locked u0) (unlock-height u0) (unlocked u1000))
- Input Signature:
(principal-destruct? (principal-address principal))
- Output Signature:
(response { hash-bytes: (buff 20), name: (optional (string-ascii 40)), version: (buff 1) } { hash-bytes: (buff 20), name: (optional (string-ascii 40)), version: (buff 1) })
A principal value represents either a set of keys, or a smart contract.
The former, called a standard principal,
is encoded as a (buff 1)
version byte, indicating the type of account
and the type of network that this principal can spend tokens on,
and a (buff 20)
public key hash, characterizing the principal's unique identity.
The latter, a contract principal, is encoded as a standard principal concatenated with
a (string-ascii 40)
contract name that identifies the code body.
principal-destruct?
will decompose a principal into its component parts. Standard principals will
be decomposed into a tuple containing the version byte and public key
hash. Decomposed contract principals contain the same data as standard
principals, as well as a name element that contains the human-readable
contract name part of the contract address.
This method returns a Response
that wraps this data as a tuple.
If the version byte of principal-address
matches the network (see is-standard
), then this method
returns the pair as its ok
value.
If the version byte of principal-address
does not match the network, then this method
returns the pair as its err
value.
In both cases, the value itself is a tuple containing three fields: a version
value as a (buff 1)
,
a hash-bytes
value as a (buff 20)
, and a name
value as an (optional (string-ascii 40))
. The name
field will only be (some ..)
if the principal is a contract principal.
Rationale: This method is meant to help smart contracts integrate with Bitcoin addresses and with other Stacks network instances' addresses. Legacy Bitcoin addresses can already be converted to Stacks addresses; the ability to decode them in Clarity empowers developers to do things like verify that a Bitcoin owner signed some data, or also controlled some Stacks data.
Examples:
(principal-destruct? 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6) ;; Returns (ok (tuple (hash-bytes 0x164247d6f2b425ac5771423ae6c80c754f7172b0) (name none) (version 0x1a)))
(principal-destruct? 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.foo) ;; Returns (ok (tuple (hash-bytes 0x164247d6f2b425ac5771423ae6c80c754f7172b0) (name (some "foo")) (version 0x1a)))
(principal-destruct? 'SP3X6QWWETNBZWGBK6DRGTR1KX50S74D3433WDGJY) ;; Returns (err (tuple (hash-bytes 0xfa6bf38ed557fe417333710d6033e9419391a320) (name none) (version 0x16)))
(principal-destruct? 'SP3X6QWWETNBZWGBK6DRGTR1KX50S74D3433WDGJY.foo) ;; Returns (err (tuple (hash-bytes 0xfa6bf38ed557fe417333710d6033e9419391a320) (name (some "foo")) (version 0x16)))
- Input Signatures:
(principal-construct? (buff 1) (buff 20))
(principal-construct? (buff 1) (buff 20) (string-ascii 40))
- Output Signature:
(response principal { error_code: uint, value: (optional principal) })
A principal value represents either a set of keys, or a smart contract.
The former, called a standard principal,
is encoded as a (buff 1)
version byte, indicating the type of account
and the type of network that this principal can spend tokens on,
and a (buff 20)
public key hash, characterizing the principal's unique identity.
The latter, a contract principal, is encoded as a standard principal concatenated with
a (string-ascii 40)
contract name that identifies the code body.
The principal-construct?
function allows users to create either standard or contract principals,
depending on which form is used. To create a standard principal,
principal-construct?
would be called with two arguments: it
takes as input a (buff 1)
which encodes the principal address's
version-byte
, a (buff 20)
which encodes the principal address's hash-bytes
.
To create a contract principal, principal-construct?
would be called with
three arguments: the (buff 1)
and (buff 20)
to represent the standard principal
that created the contract, and a (string-ascii 40)
which encodes the contract's name.
On success, this function returns either a standard principal or contract principal,
depending on whether or not the third (string-ascii 40)
argument is given.
This function returns a Response
. On success, the ok
value is a Principal
.
The err
value is a value tuple with the form { error_code: uint, value: (optional principal) }
.
If the single-byte version-byte
is in the valid range 0x00
to 0x1f
, but is not an appropriate
version byte for the current network, then the error will be u0
, and value
will contain
(some principal)
, where the wrapped value is the principal. If the version-byte
is not in this range,
however, then the value
will be none
.
If the version-byte
is a buff
of length 0, if the single-byte version-byte
is a
value greater than 0x1f
, or the hash-bytes
is a buff
of length not equal to 20, then error_code
will be u1
and value
will be None
.
If a name is given, and the name is either an empty string or contains ASCII characters
that are not allowed in contract names, then error_code
will be u2
.
Rationale: This method empowers developers to convert between Bitcoin and Stacks addresses, and between Stacks addresses and subnet addresses (with applications to enabling users to prove control of assets on different chains within a smart contract). In addition, it provides introspection for smart contract addresses.
Examples:
(principal-construct? 0x1a 0xfa6bf38ed557fe417333710d6033e9419391a320) ;; Returns (ok ST3X6QWWETNBZWGBK6DRGTR1KX50S74D3425Q1TPK)
(principal-construct? 0x1a 0xfa6bf38ed557fe417333710d6033e9419391a320 "foo") ;; Returns (ok ST3X6QWWETNBZWGBK6DRGTR1KX50S74D3425Q1TPK.foo)
(principal-construct? 0x16 0xfa6bf38ed557fe417333710d6033e9419391a320) ;; Returns (err (tuple (error_code u0) (value (some SP3X6QWWETNBZWGBK6DRGTR1KX50S74D3433WDGJY))))
(principal-construct? 0x16 0xfa6bf38ed557fe417333710d6033e9419391a320 "foo") ;; Returns (err (tuple (error_code u0) (value (some SP3X6QWWETNBZWGBK6DRGTR1KX50S74D3433WDGJY.foo))))
(principal-construct? 0x 0xfa6bf38ed557fe417333710d6033e9419391a320) ;; Returns (err (tuple (error_code u1) (value none)))
(principal-construct? 0x16 0xfa6bf38ed557fe417333710d6033e9419391a3) ;; Returns (err (tuple (error_code u1) (value none)))
(principal-construct? 0x20 0xfa6bf38ed557fe417333710d6033e9419391a320) ;; Returns (err (tuple (error_code u1) (value none)))
(principal-construct? 0x1a 0xfa6bf38ed557fe417333710d6033e9419391a320 "") ;; Returns (err (tuple (error_code u2) (value none)))
(principal-construct? 0x1a 0xfa6bf38ed557fe417333710d6033e9419391a320 "foo[") ;; Returns (err (tuple (error_code u2) (value none)))
- Input Signature:
(get-burn-block-info? (prop-name BurnBlockPropertyName) (block-height uint))
- Output Signature:
(optional buff) | (optional (tuple (addrs (list 2 (tuple (hashbytes (buff 32)) (version (buff 1))))) (payout uint)))
The get-burn-block-info?
function fetches data for a block of the given burnchain block height. The
value and type returned are determined by the specified BlockInfoPropertyName
. Valid values for block-height
only
include heights between the burnchain height at the time the Stacks chain was launched, and the last-processed burnchain
block. If the block-height
argument falls outside of this range, then none
shall be returned.
The following BurnBlockInfoPropertyName
values are defined:
-
The
header-hash
property returns a 32-byte buffer representing the header hash of the burnchain block at burnchain heightblock-height
. -
The
pox-addrs
property returns a tuple with two items: a list of up to two PoX addresses that received a PoX payout at that block height, and the amount of burnchain tokens paid to each address (note that per the blockchain consensus rules, each PoX payout will be the same for each address in the block-commit transaction). The list will include burn addresses -- that is, the unspendable addresses that miners pay to when there are no PoX addresses left to be paid. During the prepare phase, there will be exactly one burn address reported. During the reward phase, up to two burn addresses may be reported in the event that some PoX reward slots are not claimed.
The addrs
list contains the same PoX address values passed into the PoX smart contract:
- They each have type signature
(tuple (hashbytes (buff 32)) (version (buff 1)))
- The
version
field can be any of the following:0x00
means this is a pay-to-public-key-hash (p2pkh) address, andhashbytes
is the 20-byte hash160 of a single public key0x01
means this is a pay-to-script-hash (p2sh) address, andhashbytes
is the 20-byte hash160 of a redeemScript script0x02
means this is a pay-to-witness-public-key-hash-over-pay-to-script-hash (p2wpkh-p2sh) address, andhashbytes
is the 20-byte hash160 of a p2wpkh witness script0x03
means this is a pay-to-witness-script-hash-over-pay-to-script-hash (p2wsh-p2sh) address, andhashbytes
is the 20-byte hash160 of a p2wsh witness script0x04
means this is a pay-to-witness-public-key-hash (p2wpkh) address, andhashbytes
is the 20-byte hash160 of the witness script0x05
means this is a pay-to-witness-script-hash (p2wsh) address, andhashbytes
is the 32-byte sha256 of the witness script0x06
means this is a pay-to-taproot (p2tr) address, andhashbytes
is the 32-byte sha256 of the witness script
Rationale: This method empowers developers to query Bitcoin state. Stacks 2.1 adds the ability for smart contracts to query PoX payouts directly, with applications for Stacking pools and other Stacking-centric programs.
Examples:
(get-burn-block-info? header-hash u677050) ;; Returns (some 0xe67141016c88a7f1203eca0b4312f2ed141531f59303a1c267d7d83ab6b977d8)
(get-burn-block-info? pox-addrs u677050) ;; Returns (some (tuple (addrs ((tuple (hashbytes 0x395f3643cea07ec4eec73b4d9a973dcce56b9bf1) (version 0x00)) (tuple (hashbytes 0x7c6775e20e3e938d2d7e9d79ac310108ba501ddb) (version 0x01)))) (payout u123)))
- Input Signature:
(slice? (sequence sequence_A) (left-position uint) (right-position uint))
- Output Signature:
(optional sequence_A)
The slice?
function attempts to return a sub-sequence of sequence_A
that starts
at left-position
(inclusive), and ends at right-position
(non-inclusive).
If left-position
==right-position
, the function returns an empty
sequence.
If either left-position
or right-position
are out of bounds OR if
right-position
is less than left-position
, the function returns
none
.
Values in sequence_A
are zero-based. That is, the first item in sequence_A
is at index 0.
Rationale: This method facilitates parsing user-supplied encoded data, such
as data from oracles and other off-chain services. While a variant of slice?
could be implemented entirely in Clarity, it would be much slower and much more
expensive than supplying a native method.
Examples:
(slice? "blockstack" u5 u10) ;; Returns (some "stack")
(slice? (list 1 2 3 4 5) u5 u9) ;; Returns none
(slice? (list 1 2 3 4 5) u3 u4) ;; Returns (some (4))
(slice? "abcd" u0 u2) ;; Returns (some "ab")
(slice? "abcd" u1 u3) ;; Returns (some "bc")
(slice? "abcd" u2 u2) ;; Returns (some "")
(slice? "abcd" u3 u1) ;; Returns none
- Input Signature:
(string-to-int? (input (string-ascii|string-utf8)))
- Output Signature:
(optional int)
Converts a string, either string-ascii
or string-utf8
, to an
optional-wrapped signed integer. If the input string does not
represent a valid integer in decimal format, then the function returns none
. Otherwise
it returns an int
wrapped in some
.
Rationale: This method facilitates parsing user-supplied encoded data from text. While this functionality could have been implemented in Clarity, it would have been much more expensive to use.
Examples:
(string-to-int? "1") ;; Returns (some 1)
(string-to-int? u"-1") ;; Returns (some -1)
(string-to-int? "a") ;; Returns none
- Input Signature:
(string-to-uint? (input (string-ascii|string-utf8)))
- Output Signature:
(optional uint)
Converts a string, either string-ascii
or string-utf8
, to an
optional-wrapped uint
. If the input string does not represent a
valid non-negative integer in decimal format, then the function returns
none
. Otherwise it returns an uint
wrapped in some
.
Rationale: This method facilitates parsing user-supplied encoded data from text. While this functionality could have been implemented in Clarity, it would have been much more expensive to use.
Examples:
(string-to-uint? "1") ;; Returns (some u1)
(string-to-uint? u"1") ;; Returns (some u1)
(string-to-uint? "a") ;; Returns none
- Input Signature:
(int-to-ascii (input (int|uint)))
- Output Signature:
string-ascii
Converts an integer, either int
or uint
, to a string-ascii
string-value representation in decimal format.
Rationale: This method facilitates parsing user-supplied encoded data from text. While this functionality could have been implemented in Clarity, it would have been much more expensive to use.
Examples:
(int-to-ascii 1) ;; Returns "1"
(int-to-ascii u1) ;; Returns "1"
(int-to-ascii -1) ;; Returns "-1"
- Input Signature:
(int-to-utf8 (input (int|uint)))
- Output Signature:
string-utf8
Converts an integer, either int
or uint
, to a string-utf8
string-value representation in decimal format.
Rationale: This method facilitates parsing user-supplied encoded data from text. While this functionality could have been implemented in Clarity, it would have been much more expensive to use.
Examples:
(int-to-utf8 1) ;; Returns u"1"
(int-to-utf8 u1) ;; Returns u"1"
(int-to-utf8 -1) ;; Returns u"-1"
- Input Signature:
(buff-to-int-le (input (buff 16)))
- Output Signature:
int
Converts a byte buffer to a signed integer use a little-endian encoding. The byte buffer can be up to 16 bytes in length. If there are fewer than 16 bytes, as this function uses a little-endian encoding, the input behaves as if it is zero-padded on the right.
Rationale: This method facilitates parsing user-supplied encoded data, or
data available on-chain that is currently represented as a buff
.
While this functionality could have been implemented in Clarity, it would
have been much more expensive to use.
Examples:
(buff-to-int-le 0x01) ;; Returns 1
(buff-to-int-le 0x01000000000000000000000000000000) ;; Returns 1
(buff-to-int-le 0xffffffffffffffffffffffffffffffff) ;; Returns -1
(buff-to-int-le 0x) ;; Returns 0
- Input Signature:
(buff-to-uint-le (input (buff 16)))
- Output Signature:
uint
Converts a byte buffer to an unsigned integer use a little-endian encoding.. The byte buffer can be up to 16 bytes in length. If there are fewer than 16 bytes, as this function uses a little-endian encoding, the input behaves as if it is zero-padded on the right.
Rationale: This method facilitates parsing user-supplied encoded data, or
data available on-chain that is currently represented as a buff
.
While this functionality could have been implemented in Clarity, it would
have been much more expensive to use.
Examples:
(buff-to-uint-le 0x01) ;; Returns u1
(buff-to-uint-le 0x01000000000000000000000000000000) ;; Returns u1
(buff-to-uint-le 0xffffffffffffffffffffffffffffffff) ;; Returns u340282366920938463463374607431768211455
(buff-to-uint-le 0x) ;; Returns u0
- Input Signature:
(buff-to-int-be (input (buff 16)))
- Output Signature:
int
Converts a byte buffer to a signed integer use a big-endian encoding. The byte buffer can be up to 16 bytes in length. If there are fewer than 16 bytes, as this function uses a big-endian encoding, the input behaves as if it is zero-padded on the left.
Rationale: This method facilitates parsing user-supplied encoded data, or
data available on-chain that is currently represented as a buff
.
While this functionality could have been implemented in Clarity, it would
have been much more expensive to use.
Examples:
(buff-to-int-be 0x01) ;; Returns 1
(buff-to-int-be 0x00000000000000000000000000000001) ;; Returns 1
(buff-to-int-be 0xffffffffffffffffffffffffffffffff) ;; Returns -1
(buff-to-int-be 0x) ;; Returns 0
- Input Signature:
(buff-to-uint-be (input (buff 16)))
- Output Signature:
uint
Converts a byte buffer to an unsigned integer use a big-endian encoding. The byte buffer can be up to 16 bytes in length. If there are fewer than 16 bytes, as this function uses a big-endian encoding, the input behaves as if it is zero-padded on the left.
Rationale: This method facilitates parsing user-supplied encoded data, or
data available on-chain that is currently represented as a buff
.
While this functionality could have been implemented in Clarity, it would
have been much more expensive to use.
Examples:
(buff-to-uint-be 0x01) ;; Returns u1
(buff-to-uint-be 0x00000000000000000000000000000001) ;; Returns u1
(buff-to-uint-be 0xffffffffffffffffffffffffffffffff) ;; Returns u340282366920938463463374607431768211455
(buff-to-uint-be 0x) ;; Returns u0
- Input Signature:
(stx-transfer? (amount uint) (sender principal) (recipient principal) (memo (buff 34)))
- Output Signature:
(response bool uint)
stx-transfer-memo?
is similar to stx-transfer?
, except that it
adds a memo
field.
This function returns (ok true)
if the transfer
is successful, or, on an error, returns the same codes as
stx-transfer?
.
Rationale: This method facilitates integration with exchanges. Exchanges
often require a memo field for transferring funds into and out of their wallets,
often for the purposes of multiplexing one address into many user accounts.
Adding a native stx-transfer-memo?
function makes it easy for smart contracts
to interact with exchanges that follow this convention.
Examples:
(as-contract
(stx-transfer-memo? u50 'SP3X6QWWETNBZWGBK6DRGTR1KX50S74D3433WDGJY tx-sender 0x00)) ;; Returns (err u4)
(stx-transfer-memo? u60 tx-sender 'SP3X6QWWETNBZWGBK6DRGTR1KX50S74D3433WDGJY 0x010203) ;; Returns (ok true)
- Input Signature:
(is-standard (standard-or-contract principal))
- Output Signature:
bool
Tests whether standard-or-contract
matches the current network
type, and therefore represents a principal that can spend tokens on the current
network type. That is, the network is either of type mainnet
, or testnet
.
Only SPxxxx
and SMxxxx
c32check form addresses can spend tokens on
a mainnet, whereas only STxxxx
and SNxxxx
c32check forms addresses can spend
tokens on a testnet. All addresses can receive tokens, but only principal
c32check form addresses that match the network type can spend tokens on the
network. This method will return true
if and only if the principal matches
the network type, and false otherwise.
Rationale: This method facilitates authenticating principal types supplied by contract-calls or from decoded data. Because principals must have the correct version byte in order to spend tokens, ensuring contracts can perform this check is of paramount importance to avoid loss-of-funds.
Examples:
(is-standard 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6) ;; returns true on testnet and false on mainnet
(is-standard 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.foo) ;; returns true on testnet and false on mainnet
(is-standard 'SP3X6QWWETNBZWGBK6DRGTR1KX50S74D3433WDGJY) ;; returns true on mainnet and false on testnet
(is-standard 'SP3X6QWWETNBZWGBK6DRGTR1KX50S74D3433WDGJY.foo) ;; returns true on mainnet and false on testnet
(is-standard 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR) ;; returns false on both mainnet and testnet
- Input Signature:
(to-consensus-buff? x)
- Output Signature:
(optional buff))
The to-consensus-buff?
function is a special function that will serialize any
Clarity value into a buffer, using the SIP-005 serialization of the
Clarity value. Not all values can be serialized: some value's
consensus serialization is too large to fit in a Clarity buffer (this
is because of the type prefix in the consensus serialization).
If the value cannot fit as serialized into the maximum buffer size (1 MB),
this returns none
, otherwise, it will be
(some consensus-serialized-buffer)
. During type checking, the
analyzed type of the result of this method will be the maximum possible
consensus buffer length based on the inferred type of the supplied value.
Note that it is possible to construct a valid Clarity value whose buffer
serialization exceeds 1 MB; however, this is highly unlikely to happen by
accident.
Rationale: This method is used to facilitate interactions with off-chain services, and to export data from a smart contract that can be consumed off-chain.
Examples:
(to-consensus-buff? 1) ;; Returns (some 0x0000000000000000000000000000000001)
(to-consensus-buff? u1) ;; Returns (some 0x0100000000000000000000000000000001)
(to-consensus-buff? true) ;; Returns (some 0x03)
(to-consensus-buff? false) ;; Returns (some 0x04)
(to-consensus-buff? none) ;; Returns (some 0x09)
(to-consensus-buff? 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR) ;; Returns (some 0x051fa46ff88886c2ef9762d970b4d2c63678835bd39d)
(to-consensus-buff? { abc: 3, def: 4 }) ;; Returns (some 0x0c00000002036162630000000000000000000000000000000003036465660000000000000000000000000000000004)
- Input Signature:
(from-consensus-buff? type-signature buff)
- Output Signature:
(optional t)
The from-consensus-buff?
function is a special function that will deserialize a
buffer into a Clarity value, using the SIP-005 serialization of the
Clarity value. The type that from-consensus-buff?
tries to deserialize
into is provided by type-signature
. If it fails
to deserialize the buff
argument to this type, the method returns none
.
Note that the given buff
argument cannot represent more than 1 MB of data.
Rationale: This method is used to facilitate interactions with off-chain services, and to implement "transaction-less transactions."" The ability to both verify the signature on a buffer and decode the buffer into a Clarity value is a basic building block for bridges and oracles. In addition, it is a basic building block for smart contracts where users are not expected to send transactions (e.g. because they lack a wallet, or because the transaction volume would be too high). Instead, they can send signed Clarity values to someone willing to package them into transactions at a later time (possibly as a batch), and send them to the smart contract on their behalf.
Examples:
(from-consensus-buff? int 0x0000000000000000000000000000000001) ;; Returns (some 1)
(from-consensus-buff? uint 0x0000000000000000000000000000000001) ;; Returns none
(from-consensus-buff? uint 0x0100000000000000000000000000000001) ;; Returns (some u1)
(from-consensus-buff? bool 0x0000000000000000000000000000000001) ;; Returns none
(from-consensus-buff? bool 0x03) ;; Returns (some true)
(from-consensus-buff? bool 0x04) ;; Returns (some false)
(from-consensus-buff? principal 0x051fa46ff88886c2ef9762d970b4d2c63678835bd39d) ;; Returns (some SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR)
(from-consensus-buff? { abc: int, def: int } 0x0c00000002036162630000000000000000000000000000000003036465660000000000000000000000000000000004) ;; Returns (some (tuple (abc 3) (def 4)))
- Input Signature:
(replace-at? (x (sequence Y)) (i uint) (value Y))
- Output Signature:
(optional (sequence Y))
The replace-at?
function takes in a sequence, an index, and an element,
and returns a new sequence with the data at the index position replaced with the given element.
The supported sequence types are string-ascii
, string-utf8
, buff
, and
list
.
-
If
(sequence Y)
is either(string-ascii ...)
or(string-utf8...)
, then(value Y)
must be a(string-ascii 1)
or a(string-utf8 1)
, respectively. -
If
(sequence Y)
is(buff ...)
, then(value Y)
must be a(buff 1)
. -
If
(sequence Y)
is a(list Y ...)
(i.e. a list of values of typeY
), then(value Y)
must be a value of typeY
.
If the provided index is out of bounds, this functions returns none
.
Note that sequences are zero-indexed. The first item in x
is at index 0.
Rationale: This method makes it possible to set values of a sequence type without incurring the costs of splitting the sequence into subsequences, and concatenating them back together with the new value. This operation is also a common facility in many other languages, so supporting it in Clarity would make Clarity more approachable to new developers.
Examples:
(replace-at? u"ab" u1 u"c") ;; Returns (some u"ac")
(replace-at? 0x00112233 u2 0x44) ;; Returns (some 0x00114433)
(replace-at? "abcd" u3 "e") ;; Returns (some "abce")
(replace-at? (list 1) u0 10) ;; Returns (some (10))
(replace-at? (list (list 1) (list 2)) u0 (list 33)) ;; Returns (some ((33) (2)))
(replace-at? (list 1 2) u3 4) ;; Returns none
- Type:
(optional principal)
This global variable evaluates to the fee-sponsoring principal of the current transaction (if there is such a principal).
Rationale: Stacks already exposes the origin address as tx-sender
, and
exposing this data would make it possible to build smart contracts where only
certain entities could relay transactions on behalf of users (such as entities
with which the user has an off-chain business relationship).
- Type:
uint
This global variable evaluates to a 32-bit chain ID that identifies this instance of the Stacks blockchain. The purpose of this global variable is to give contract developers a way to determine which instance of Stacks their code runs on, in order to execute instance-specific logic or integrate with instance-specific off-chain services.
The following values are reported:
-
u1
: This is the Stacks mainnet chain (this is0x00000001
). -
u2147483648
: This is the Stacks testnet chain (this is0x80000000
).
Other values may be used for other deployments of Stacks, such as (but not limited to) subnets.
Rationale: Stacks already has two instances (testnet and mainnet), and subnets are instances of Stacks. Enabling smart contracts to differentiate between which environments they run in would empower developers to add (or rely on) chain-specific features.
This method aliases the existing method element-at
, which returns the element at the specified index in the provided sequence.
Rationale: This alias is added so that there is consistent usage of the ?
suffix in builtin Clarity methods (used when the return value is a response
or an optional
). element-at
from Clarity1 is left for compatibility, though tools should encourage use of the new spelling.
This method aliases the existing method index-of
, which returns the first index at which the specified item can be found in the provided sequence.
Rationale: This alias is added so that there is consistent usage of the ?
suffix in builtin Clarity methods (used when the return value is a response
or an optional
). index-of
from Clarity1 is left for compatibility, though tools should encourage use of the new spelling.
This SIP proposes a new Clarity lexer and parser implementation that will be specific to Clarity 2. Current benchmarks indicate that the reference implementation of this new lexer and parser it is around 3x faster than the reference implementation of the chain today.
The reason for proposing this as a breaking change is to de-risk the possibility that the new lexer and parser are not "bug-for-bug" compatible with the current parser. Tying the release of this new lexer and parser with a breaking change, while keeping the old parser for processing all blocks prior to the change, ensures that any new behaviors introduced will only apply to transactions after the change takes effect.
Over the lifetime of Clarity 1, it was discovered that there are a few surprising behaviors of the current trait semantics. Clarity 2 proposes the following new trait semantics:
-
Traits are real types. The Clarity 2 type system will analyze and propagate the type of a trait value in the same ways that it does with other types. This means that in Clarity 2, trait values can be embedded into complex types (lists, tuples, optionals, and responses) and maintain their ability to enable dynamic dispatch. The only limitation on the trait type is that trait values may not be persisted (e.g. in a data-var or map). Persisting a trait value, and maintaining the ability to perform dynamic dispatch would inhibit static analysis of a contract.
-
Trait values may be coerced into compatible trait types. When type checking a trait value against another trait type, the expected trait type need not be the identical trait type of the value; instead, the trait value is checked for compatibility with the expected type, meaning that if the expected trait type is a subset of the trait value's type, then the coercion is legal. See below for an example.
-
Trait values may be locally bound. A trait value can be bound in a local scope and maintain its trait type and dynamic dispatch functionality. Binding in a local scope includes
let
expressions andmatch
expressions. -
Traits may not be defined with duplicate function names. In Clarity 1 a trait could be defined with duplicated function names in its signature. This resulted in only the last instance (in program order) being kept as part of the trait's definition. In Clarity 2, a trait defined with a duplicate function name will trigger an analysis error, preventing it from being mined in a block.
-
Contract principals stored in constants are callable. Clarity 2 allows a constant contract principal (defined with a
define-constant
) to be callable -- used to make static dispatch contract calls, or coerced into a trait type. -
Imported trait name conflicts. In Clarity 1, there is surprising behavior relating to the names of imported traits (from
use-trait
). The name of a local trait will conflict with the trait name of the imported trait. For example, in(use-trait a-alias .a-trait.a)
if a local trait is defined with the namea
, then uses ofa-alias
will refer to the local trait,a
, instead of the imported trait. In Clarity 2, the imported trait can always be referenced by its alias and the imported trait name will not conflict with local traits.
Clarity 1 trait semantics are preserved in Clarity 1 code.
;; use trait in optional
(define-public (execute (job (optional <job-trait>))) (match job j (contract-call? j fn) (default-job)))
;; use of compatible traits, e.g. marketplace commissions
(define-trait t1 ((fn (response uint bool))))
(define-trait t2 ((fn (response uint bool))))
;; --- contract 1
(impl-trait .contract.t1)
;; ...
;; -- contract 2
(impl-trait .contract.t2)
;; ...
;; -- contract 3
(use-trait t contract.t1)
;; can be called with contract 1 and contract 2 because of compatible traits
(define-public (meta-fn (ctr <t>)...)
In Clarity version 2, these binary comparators will be extended to support
comparison of string-ascii
, string-utf8
and buff
.
These comparisons are executed as follows:
-
For
buff
andstring-ascii
, comparison is done on a byte-by-byte basis until a difference is found, or until the end of one sequence is reached. In the first case, the differing bytes are compared with the comparator. In the second case, the shorter of the two sequences is considered to be "less than" the other. -
For
string-utf8
, comparison is done on a codepoint-by-codepoint basis. Each codepoint occupies between 1 and 4 bytes. Comparing two codepoints is the act of comparing them on a byte-by-byte basis, just as if they were(buff 4)
s. Comparison proceeds until either a difference is found, or until the end of one sequence is reached. In the first case, the differing codepoints are compared with the comparator. In the second case, the shorter of the two sequences (as measured by number of codepoints) is considered to be "less than" the other.
Examples:
(>= "baa" "aaa") ;; Returns true
(>= "aaa" "aa") ;; Returns true
(>= 0x02 0x01) ;; Returns true
(> "baa" "aaa") ;; Returns true
(> "aaa" "aa") ;; Returns true
(> 0x02 0x01) ;; Returns true
(< "aaa" "baa") ;; Returns true
(< "aa" "aaa") ;; Returns true
(< 0x01 0x02) ;; Returns true
(< 0x01 0x0100) ;; Returns true
(< 0x01ff 0x01) ;; Returns false
(> 0x01ff 0x01) ;; Returns true
(< 0x01ff 0x02) ;; Returns true
(< u"\u{0380}" u"Z") ;; Returns false
(< u"\u{5a}" u"\u{5b}") ;; Returns true
(<= u"\u{5a}" u"Z") ;; Returns true
(>= u"\u{5a}" u"Z") ;; Returns true
(< u"stacks" u"st\u{c3a4}cks") ;; Returns true
The following new properties are proposed to be added get-block-info?
:
-
block-reward
: This property returns auint
value for the total block reward of the indicated Stacks block. This value is only available once the reward for the block matures. That is, the latestblock-reward
value available is at least 101 Stacks blocks in the past (on mainnet). The reward includes the coinbase, the anchored block's transaction fees, and the shares of the confirmed and produced microblock transaction fees earned by this block's miner. Note that this value may be smaller than the Stacks coinbase at this height, because the miner may have been punished with a validPoisonMicroblock
transaction in the event that the miner published two or more microblock stream forks. -
miner-spend-total
: This property returns auint
value for the total number of burnchain tokens (i.e. satoshis) spent by all miners trying to win this block. This does not include burnchain transaction fees. -
miner-spend-winner
: This property returns auint
value for the number of burnchain tokens (i.e. satoshis) spent by the winning miner for this Stacks block. Note that this value is less than or equal to the value forminer-spend-total
at the same block height. This does not include burnchain transaction fees.
Rationale: These changes to get-block-info?
empower developers to
determine how many burnchain tokens (e.g. satoshis) are spent for STX on a
block-by-block basis, without having to rely on an off-chain oracle.
The principal-of?
function returns the principal derived from the
provided public key.
If the public-key
is invalid, it will return the error code (err u1).
.
Before Stacks 2.1, this function has a bug, in that the principal returned would always be a testnet single-signature principal, even if the function were run on the mainnet. In Clarity version 2, this bug is fixed, so that this function will return a principal suited to the network it is called on. In particular, if this is called on the mainnet, it will return a single-signature mainnet principal.
To enable miners to pay their block rewards to a cold wallet address, and to enable mining pools to pay block rewards to a custodian smart contract address, the Coinbase transaction payload is altered to include an alternative address to which the block reward will materialize. Its encoding is described as follows:
- Payload Type ID: 0x05
- Payload:
- Coinbase Memo: 32 bytes
- Recipient Address: variable bytes
- This is an encoded Clarity
principal
value. See SIP-005 for details.
- This is an encoded Clarity
To support offering developers a choice to use Clarity 1 or Clarity 2, this SIP proposes a new Stacks transaction payload variant for deploying a versioned smart contract. Its encoding is described as follows:
- Payload Type ID: 0x06
- Payload:
- Clarity Version: 1 byte
- 0x01: this is a Clarity 1 contract
- 0x02: this is a Clarity 2 contract
- Smart-contract Payload: variable bytes
- This is identical to the smart-contract payload definition in SIP-005. It contains a length-prefixed name and a length-prefixed code body. See SIP-005 for details.
- Clarity Version: 1 byte
This SIP proposes adding support for issuing a delegate-stx
call to PoX
from the burnchain, similar to how stx-transfer?
and stack-stx
are supported
as burnchain-hosted transactions in SIP-007. The rationale for this is that
users may wish to maintain ownership of their STX tokens via burnchain
addresses, but permit Stacking them via Stacks addresses. This way, the Stacks
blockchain will be better able to accommodate users who already have set up
and secured burnchain wallets to hold their STX.
The burnchain wire format is as follows:
-
The first transaction input must consume a
PreSTX
operation's second output (see SIP-007). -
The first transaction output must contain an
OP_RETURN
payload:- Bytes 0-2 are the magic bytes that identify this as a Stacks transaction
- Bytes 2-3 is the opcode, which shall be
#
(0x23). - Bytes 3-19 is the number of uSTX to delegate, as a 128-bit big-endian
integer. This corresponds to the
amount-ustx
argument todelegate-stx
. - Bytes 19-23 is the optional index of the transaction output.
- If Byte 19 is set to
0x00
, then this field is ignored. This corresponds to passingnone
to thepox-addr
argument indelegate-stx
. - If Byte 19 is set to
0x01
, then bytes 20-23 are interpreted as a 32-bit big-endian integer, which shall index one of the transaction outputs after theOP_RETURN
output. For example, the output at index 0 is the first output after theOP_RETURN
output, index 1 is the second after, etc. The output's address, if it decodes to a valid PoX address tuple, will be used as thepox-addr
argument todelegate-stx
.
- If Byte 19 is set to
- Bytes 24-33 is the optional last burnchain block height for which these
uSTX are delegated.
- If Byte 24 is set to
0x00
, then this field is ignored. It corresponds to passingnone
to theuntil-burn-ht
argument indelegate-stx
. - If Byte 24 is set to
0x01
, then this field is the 128-bit big-endian integer that encodes the burnchain block height at which this delegation expires. This value corresponds to theuntil-burn-ht
argument indelegate-stx
.
- If Byte 24 is set to
-
The second transaction output encodes the Stacks address to which the STX are delegated. This output must decode to a Stacks address. This field corresponds to the
delegate-to
argument indelegate-stx
.
If the index that represents pox-addr
in bytes 19-23 does not correspond
to an existing output, or does not decode to a valid PoX address, then the
transaction is ignored.
The effect of sending a well-formed burnchain transaction with the above data is
to materialize a contract-call to delegate-stx
in pox-2
with the
aforementioned argument values. Calls to delegate-stx
will happen after the
calls to stack-stx
and transfer-stx
are materialized from burnchain
transactions.
This SIP proposes making the history of PoX anchor blocks itself forkable, and by implementing Nakamoto consensus on the PoX anchor block history forks so that there will always be a canonical PoX anchor block history. In doing so, the Stacks blockchain now has three levels of forks: the Bitcoin chain, the history of PoX anchor blocks, and the history of Stacks blocks. The canonical Stacks fork is the longest history of Stacks blocks that passes through the canonical history of anchor blocks which resides on the canonical Bitcoin chain.
To enable this, this SIP proposes a couple additional requirements for PoX anchor block selection over those in SIP-007. The rationale for these changes is to make it possible to identify the PoX anchor block's block-commit without having the Stacks chain state. The reason this is important is because it allows the honest miners of the network to identify and ignore a hidden PoX anchor block that is no longer on the canonical Stacks fork (should it be found later), which in turn prevents a hidden PoX anchor block's arrival from triggering a deep chain reorganization. The two additional requirements for PoX anchor blocks are:
-
Heaviest block-commit. The PoX anchor block must be mined in a block-commit whose descendant block-commits in the prepare phase collectively represent the most burnchain tokens (i.e. satoshis) burnt, out of all candidates. This will statistically almost always be the case if there is no or little contention between miners, but in the event that two or more histories of block-commits arise which both (a) span at least 80 Bitcoin blocks and (b) descend from conflicting PoX anchor blocks, then this rule is used to break ties. In the event that two such histories arise that burn an equal amount of BTC, the PoX anchor block-commit which occurs higher in the burnchain is selected. Note that at most one of these block-commit histories will correspond to valid Stacks blocks, since each Bitcoin block in a prepare phase chooses at most one block-commit as corresponding to a Stacks block in any Stacks fork.
-
A block can be a PoX anchor block at most once. Today, miners can select the same Stacks block as the PoX anchor block over and over. This SIP proposes removing this possibility.
When these two changes are applied, it becomes possible for nodes to inspect
the burnchain transactions and determine the degree of affirmation each PoX
anchor block selection has received by the network in subsequent prepare phases.
The miners implicitly affirm the block's presence or absence when they make the
choice as to whether or not to mine off of a descendant. With these changes, a
node can determine which block-commits correspond to PoX anchor blocks, and
from there, determine how many subsequent PoX anchor blocks have built on a
previous PoX anchor block.
With this knowledge, the node can partition the set of Stacks forks that could exist (based on the block-commits) into disjoint fork sets, where two forks are in the same set if they contain the same sequence of PoX anchor blocks. The canonical fork must contain the highest possible number of PoX anchor blocks; all forks that contain fewer than this must be non-canonical. The node then proceeds to ignore (i.e. store but not process) PoX anchor blocks for forks classified as non-canonical in this manner.
The node only processes a deep reorganization -- i.e. switching its view of the canonical Stacks fork to a Stacks fork that contains a different set of PoX anchor blocks -- once there exists a Stacks fork with more PoX anchor blocks than the one it currently treats as canonical. This would only happen if the miners of the network had mined sufficiently many PoX anchor blocks in subsequent prepare phases to have made canonical a fork from a different fork set. The barrier to entry for miners doing this is quite high: they must repeatedly select PoX anchor blocks in multiple prepare phases -- more than those which have affirmed the current canonical fork -- and they must do so without 20% or more opposition each time. This makes executing a deep reorg much harder to do for a malicious miner than it is today, because the malicious miner must repeatedly out-mine the defenders in each subsequent reward cycle (and must consistently do so at a level of 80% or higher). This is what we mean when we say that PoX anchor blocks are now forkable, and follow Nakamoto consensus.
In the event that two fork sets represent different PoX anchor blocks, but represent the same number of PoX anchor blocks, the canonical fork will reside in the fork set whose PoX anchor block was mined last.
Details on how this feature works can be found in [6] and [7].
To address the usability limitations of on-burnchain Stacks operations, this SIP proposes allowing these transactions to be processed in all Stacks forks built upon within the six Bitcoin blocks mined after such a transaction is mined. Today, SIP-007 only requires that they are considered in the Stacks block mined in the subsequent burnchain block (if one exists at all).
The reason for this change is because the existence of orphaned Stacks blocks and burnchain blocks with no sortition (i.e. "flashblocks") are the root cause of the poor user experience for on-burnchain transactions. Today, if a burnchain operation is mined in burnchain block N, and burnchain block N+1 either has no Stacks block selected or selects an orphaned Stacks block, then the on-burnchain operation does not take effect in the canonical Stacks fork. The user will have wasted their time and burnchain tokens (i.e. satoshis) attempting to use this facility, and will be forced to retry the operation.
This proposal increases the period for which an on-burnchain operation will be considered. Instead of considering them for the Stacks block mined in burnchain block N+1, they will be considered in all Stacks blocks mined in burnchain blocks N+1 through N+6, inclusive.
When mining a block, miners track the on-burnchain transactions from the last 6 burnchain blocks and determine whether or not they have been applied to an ancestor of the block they are mining. If not, then they are applied to this block. Miners will not apply the on-burnchain operations on subsequent Stacks blocks built off this block.
Prior to 2.1, the transaction-processing logic followed this procedure:
- Verify that the spending account has enough balance to pay the fee
- Run the transaction
- Debit the fee from the spending account
The benefits of this approach are two-fold. First, a transaction can earn the paying account the requisite STX to pay its fee in step 3, so a too-low balance is not necessarily a barrier-to-entry for new users. Second, a user will not be penalized in a contract-call or contract-publish transaction if the act of running the transaction spends more STX than they anticipated.
The problem with this approach in practice is that it creates too much work for miners. If the account did not have enough STX left over at the end of step 2, then step 3 could not complete, and the transaction could not be mined. This creates a problem for miners, who are obliged to process the transaction in step 2 without knowing if an insufficient balance condition could arise in step 3. Miners are not compensated for this work. The reference implementation addresses this problem by never re-considering such transactions if they are detected, and never propagating them.
In 2.1, this problem is addressed by an alternative processing procedure:
- Verify that the spending account has enough balance to pay the fee
- Debit the fee from the spending account
- Run the transaction
While this means that the benefits of the old approach are lost, it is the
authors' opinion that the upside of ensuring miners get paid for processing
transactions more than make up for this. Specifically, this change means that
a user's pending transactions cannot be blocked by a dependent transaction in
the mempool which is unmineable simply because the balance was ultimately too
low. Eliminating this class of errors with this alternative processing
procedure is deemed to itself be beneficial to the user experience above and
beyond what the original benefits offerred. Furthermore, both the newly-added
to-consensus-buff?
and from-consensus-buff?
Clarity functions and the
existence of sposnored transactions already enable users to send signed Clarity
data to the Stacks blockchain without possessing any STX of their own.
When processing a transaction, a transaction can fail for one of the following two reasons:
-
A runtime error can occur, in which the transaction attempts to do something the VM prohibits. For example, divide-by-0 is a runtime error, as is spending tokens that the caller does not possess. Today, a transaction that encounters a runtime error can be included in a block without invalidating the block.
-
A check error can occur, in which the transaction attempts to do something prohibited by the Clarity language itself. For example, a transaction can use
(at-block 0xabc)
to attempt to call a smart contract method in a trait implementation which was instantiated after the block0xabc
was mined. Attempting to call a function in a contract that does not exist is prohibited by the Clarity language -- such an action cannot be expressed in Clarity. Today, a transaction that encounters a check error cannot be included in a valid block.
Most check errors can be caught in the analysis pass of publishing a smart contract, which would then prohibit them from being published. However, there are some classes of check errors (such as the one described above) where this is infeasible.
This SIP proposes treating all check errors that arise during transaction-processing as runtime errors. Transactions that encounter check errors would be mined, and their fees would be collected, but the transaction's payload would never be applied to the chainstate.
This change benefits both miners and users. With this change, miners will get paid for processing a transaction regardless of how it would fail when executed. Notably, this change does not prevent the user's wallet from executing a more-rigorous analysis pass on any transactions the user sends.
This change also helps users because it means that transactions that encounter check errors will get cleared from the mempool without any further action on their parts. Today, users must replace-by-fee (RBF) the problematic transaction.
The behavior of the Smart-contract Payload transaction payload variant (see SIP-005) is changed such that the Clarity version used to evaluate a new contract shall be determined by the current system epoch. Right now, the system epoch is 2.05, in which case, a new smart contract instantiated this way shall be processed with Clarity 1 rules. In epoch 2.1, a new smart contract instantiated this way shall be processed with Clarity 2 rules. Users who wish to publish Clarity 1 contracts in epoch 2.1 can use the versioned smart contract payload described above.
Today, a Stacks miner's probability of winning a sortition is proportional to the minimum of the spend from last block-commit mined, and the median spend of the last six block-commits sent. Because a block-commit transaction can be "late" -- i.e. it may not be mined in the intended burnchain block, but in a subsequent one -- the median block-commit spend is calculated as the median of the last six transactions sent by the miner, including those that were late.
There was a bug in the implementation of this calculation whereby a late block-commit transaction was treated as absent [8]. This not only prevented this block-commit's spend from being considered, but it also prevented all of the miner's prior block-commits from being considered. As a result, the act of missing a block-commit had unduely negative impacts on mining -- for the next two blocks, their median block-commit spend would be calculated as zero.
Upon further review from the Economics Consideration Advisory Board, it was determined that the original algorithm was also flawed. If a miner's previous six transactions would be considered regardless of how late they were, then a miner could send all six block-commits in the same burnchain block and have a non-zero chance of winning a Stacks block. This runs counter to the incentives this algorithm was designed to create, which were to encourage miners to mine persistently.
Furthermore, in the act of validating this finding from the Economics CAB, it was discovered that the reference implementation does not check if a late block-commit paid to the intended burnchain block's PoX recipients. This is a logic bug that can only be fixed in a breaking change. Failure to fix this bug would further mean that a miner could simply pay themselves in block-commits they knew would be treated as late. This in turn would mean that a miner could (a) mine sporadically at the cost of a higher burnchain transaction fee, and (b) discount-mine profitably if the miner happend to also own the upcoming PoX reward slots.
To address these findings, this SIP proposes two changes to how the grace period for late block-commits is handled:
-
A block-commit can be late by no more than one burnchain block. If it is late by two or more burnchain blocks, then none of its prior block-commits' spends will be considered for the median spend calculation when the miner mines next. This means that a miner must have mined in at least two consecutive burnchain blocks in order to have a non-zero chance of winning (i.e. their median spend would be half of the lower of the two spends). This also means that a miner's median spend calculation will not be affected by block-commits being late by one block, which is by far the most common miss distance for late block-commits.
-
A block-commit must always pay to its intended burnchain block's PoX recipients, even if it is late. If a miner does not do this for a late block-commit, their prior block-commits are not considered for their subsequent median spend calculations.
Most blockchains regularly execute coordinated breaking changes to add new features or behaviors. Prominent examples include Ethereum [2] and Tezos [3]. Indeed, Stacks has already gone through one breaking change [4] via its SIP process.
Stacks strikes a balance between developer-coordinated breaking changes (like Ethereum) and self-amending ledgers (like Tezos). Like Ethereum, changes in Stacks are sufficiently disruptive that for now, upgrades must be coordinated out-of-band and are led by developers. But unlike Ethereum, the Stacks SIP process [5] has well-defined constraints on developers' ability to effect changes unilaterally -- in particular, Stacks' governance system requires independent reviewers to authorize breaking changes, and in the case of breaking changes, requires (through precedent) users to explicitly consent to them in-band through coin votes.
This SIP retains full backwards-compatibility with the Stacks 2.05 chainstate. All Clarity features that are supported today will continue to be supported. However, developers publishing new Clarity 1 contracts will need to use the new contract-publish transaction and explicitly indicate that they want to use Clarity 1. All smart contracts published with the standard contract-publish transaction will default to using Clarity 2 rules.
Due to the introduction of new reserved keywords in Clarity 2, it will not be possible to publish an already-written contract as a Clarity 2 contract if it uses these reserved words for any other purpose. However, already-deployed contracts are not affected -- they were published under Clarity 1 rules, and will continue to be usable as-is.
As mentioned above, Stacking will no longer be possible in the pox
contract. Calls to
Stacking operations in pox
will fail. All future Stacking operations happen
through the new pox-2
contract. However, the read-only contract calls in pox
will continue to be available for posterity.
All Stacked STX in pox
will automatically unlock when this SIP activates, even if
they were locked for subsequent reward cycles. Users will have a chance to re-Stack
their STX into the new pox-2
contract in order to continue participating in
PoX once the subsequent reward cycle begins.
Because this SIP proposes a breaking change it is of paramount importance that the ecosystem find this proposal acceptable. To measure this, three sets of criteria must be met in order to determine that there is sufficient support for this SIP: one set for Stacked STX holders, one set for un-Stacked STX holders, and one set for miners. These critera are meant to broaden the set of participating users above and beyond what has been done in the past. In particular, users who do not Stack are invited to vote on this SIP, as are users who Stack in pools.
In all cases, voting will take place during reward cycles 46 and 47. This window is estimated to begin starting November 10, 2022 and ending December 8, 2022.
In order for this SIP to activate, the following criteria must be met by the set of Stacked STX:
-
At least 80 million Stacked STX must vote at all to activate this SIP. This number is chosen because it is more than double the amount of STX locked by the largest Stacker at the time of this writing (reward cycle 44).
-
Of the Stacked STX that vote, at least 80% of them must vote "yes."
The act of not voting is the act of siding with the outcome, whatever it may be. We believe that these thresholds are sufficient to demonstrate interest from Stackers -- Stacks users who have a long-term interest in the Stacks blockchain's succesful operation -- in performing this upgrade.
If a user is Stacking, then their STX can be used to vote in one of two ways, depending on whether or not they are solo-stacking or stacking through a delegate.
The user must be Stacking in both reward cycles 46 and 47. Their vote contribution will be the number of STX they have locked. If the user has not Stacked in either cycle, then they may vote instead as a non-stacker (see next section).
The user must send a minimal amount of BTC from their PoX reward address to one of the following Bitcoin addresses:
-
For "yes", the address is
11111111111111X6zHB1ZC2FmtnqJ
. This is the base58check encoding of the hash in the Bitcoin scriptOP_DUP OP_HASH160 000000000000000000000000007965732d322e31 OP_EQUALVERIFY OP_CHECKSIG
. The value000000000000000000000000007965732d322e31
encodes "yes-2.1" in ASCII, with 0-padding. -
For "no", the address is
1111111111111117CrbcZgemVNFx8
. This is the base58check encoding of the hash in the Bitcoin scriptOP_DUP OP_HASH160 00000000000000000000000000006e6f2d322e31 OP_EQUALVERIFY OP_CHECKSIG
. The value00000000000000000000000000006e6f2d322e31
encodes "no-2.1" in ASCII, with 0-padding.
From there, the vote tabulation software will track the Bitcoin transaction back
to the PoX address in the .pox
contract that sent it, and identify the
quantity of STX it represents. The STX will count towards a "yes" or "no" based
on the Bitcoin address the PoX address sends to.
If the PoX address holder votes for both "yes" and "no" by the end of the vote, the vote will be discarded.
Note that this voting procedure does not apply to Stacking pool operators. Stacking pool operator votes will not be considered.
If the user is stacking in a pool, then they must send a minimal amount of STX from their Stacking address to one of the following Stacks addresses to commit their STX to a vote:
-
For "yes", the address is
SP00000000000003SCNSJTCHE66N2PXHX
. This is the c32check-encoded Bitcoin address for "yes" (11111111111111X6zHB1ZC2FmtnqJ
) above. -
For "no", the address is
SP00000000000000DSQJTCHE66XE1NHQ
. This is the c32check-encoded Bitcoin address for "no" (1111111111111117CrbcZgemVNFx8
) above.
From there, the vote tabulation software will track the STX back to the sender, and verify that the sender also has STX stacked in a pool. The Stacked STX will be tabulated as a "yes" or "no" depending on which of the above two addresses receive a minimal amount of STX.
If the Stacks address holder votes for both "yes" and "no" by the end of the vote period, the vote will be discarded.
If the user is not Stacking, then they can still vote with their liquid STX. To facilitate this, the user would vote "yes" or "no" for a forthcoming Stacks 2.1 proposal in EcosystemDAO [1]. The text of this proposal shall be this SIP. The STX in the user's Hiro Web Wallet will be tabulated to count for a "yes" or "no".
To prevent an influx of new STX holders from throwing the vote, a snapshot of all Stacks balances and lock-up states will be used to determine how many STX a user can commit to a "yes" or "no" vote. The snapshot will be back-dated to a Stacks block prior to this SIP's activation, whose hash and height shall be provided here when voting begins.
To prevent whales who possess many liquid STX from throwing the vote, each address will only be permitted to vote with a maximum amount of STX equal to the reward cycle's minimum Stacking threshold in the reward cycle in which they vote. This, combined with the snapshot, prevents whales from having too much influence, even if they distributed their STX across many addresses. For example, if Alice has 1 million liquid STX, but votes in a reward cycle in which the minimum Stacking threshold is 120,000 STX, then only 120,000 of her STX will be counted.
For this SIP to activate, a 66% majority of liquid STX must vote "yes". There is no threshold for how many STX must participate.
There is only one criterion for miners to activate this SIP: they must mine the Stacks blockchain up to and past the end of the voting period. In all reward cycles between cycle 45 and the end of the voting period, PoX must activate.
Suppose Alice has stacked 100,000 STX to 1LP3pniXxjSMqyLmrKHpdmoYfsDvwMMSxJ
during at least one of the voting period's reward cycles. To vote,
she sends 5500 satoshis for yes to 11111111111111X6zHB1ZC2FmtnqJ
. Then, her 100,000
STX are tabulated as "yes".
Suppose Bob has Stacked 1,000 STX in a Stacking pool and wants to vote "no", and
suppose it remains locked in PoX during at least one reward cycle in the voting
period. Suppose his Stacks address is SP2REA2WBSD3XMVMYS48NJKS3WB22JTQNB101XRRZ
. To
vote, he sends 1 uSTX from SP2REA2WBSD3XMVMYS48NJKS3WB22JTQNB101XRRZ
for no to
SP00000000000000DSQJTCHE66XE1NHQ
. Then, his 1,000 STX are tabulated as "no."
Suppose Charlie owns 1,000 STX in his wallet at the time of the snapshot, and he wants to vote "yes." To do this, he would sign into EcosystemDAO (https://stx.eco) with his Web wallet, select the Stacks 2.1 proposal, and select "yes." If he does not spend or Stack any STX over the voting period, then his 1,000 STX are tabulated as "yes."
Suppose Danielle owns 1,000 STX in her wallet at the time of the snapshot, and she wants to vote "no." After voting as Charlie had done in the previous example, she sends 200 STX to an exchange, and later receives 30 STX, and finally unlocks 2,000 STX from a Stacking pool. In total, only 800 of her STX will be tabulated as "no."
Suppose Erik owns 1,000 STX in his wallet at the time of the snapshot, and he wants to vote "yes." After voting via EcosystemDAO as Charlie and Danielle have, he Stacks 900 of his STX in the first voting period reward cycle. When the second reward cycle comes, he votes with his Stacked STX address for "no". In this scenario, 1000 of Erik's STX will have counted towards "yes" for the non-Stacker vote criteria, and 900 of his STX will have counted towards "no" for the Stacker vote criteria.
At the end of cycle 47, the following vote was calculated. A total of 142,239,429 STX participated.
-
For solo stacking, 135,287,693 STX voted YES (from 36 accounts), and 0 voted NO.
-
For pool stacking, 6,173,363 STX voted YES (from 176 accounts), and 5,000 voted NO (from 2 accounts).
-
For non-stackers, 612,756 STX voted YES (from 262 accounts), and 159,616 voted NO (from 5 accounts).
All voting criteria from STX holders have been met. A breakdown of the transactions can be found here.
A copy of the scripts used to tabulate the solo and pool stacking can be found here.
The reference implementation of this SIP can be found in the develop
branch of
the Stacks blockchain reference implementation. It is available at
https://github.com/stacks-network/stacks-blockchain.
The reference implementation is known to have the following inconsistencies with SIP-015:
get-burn-block-info?
only returns data for Bitcoin blocks of the grandparent block. It does not return data for the parent of the block in which it is evaluated.
[1] https://github.com/Clarity-Innovation-Lab/ecosystem-dao
[2] https://ethereum.org/en/history/
[3] https://opentezos.com/tezos-basics/history-of-amendments/
[6] https://docs.google.com/presentation/d/1iXvQlVZJ30xEB25v3ILHlcKU8eXB9MqpcMUPtoISpM4/edit?usp=sharing
- Passcode: nHU@4ENY