Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions pox-locking/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use clarity::vm::ast::ASTRules;
use clarity::vm::contexts::GlobalContext;
use clarity::vm::errors::Error as ClarityError;
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, TupleData};
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, ResponseData, TupleData};
use clarity::vm::Value;
#[cfg(test)]
use slog::slog_debug;
Expand All @@ -31,9 +31,11 @@ use stacks_common::{error, test_debug};
/// - for delegate stacking functions, it's the first argument
fn get_stacker(sender: &PrincipalData, function_name: &str, args: &[Value]) -> Value {
match function_name {
"stack-stx" | "stack-increase" | "stack-extend" | "delegate-stx" => {
Value::Principal(sender.clone())
}
"stack-stx"
| "stack-increase"
| "stack-extend"
| "delegate-stx"
| "revoke-delegate-stx" => Value::Principal(sender.clone()),
_ => args[0].clone(),
}
}
Expand Down Expand Up @@ -100,7 +102,11 @@ fn create_event_info_aggregation_code(function_name: &str) -> String {
}

/// Craft the code snippet to generate the method-specific `data` payload
fn create_event_info_data_code(function_name: &str, args: &[Value]) -> String {
fn create_event_info_data_code(
function_name: &str,
args: &[Value],
response: &ResponseData,
) -> String {
match function_name {
"stack-stx" => {
format!(
Expand Down Expand Up @@ -335,11 +341,31 @@ fn create_event_info_data_code(function_name: &str, args: &[Value]) -> String {
pox_addr = &args[3],
)
}
_ => "{{ data: {{ unimplemented: true }} }}".into(),
"revoke-delegate-stx" => {
if let Value::Optional(opt) = *response.data.clone() {
format!(
r#"
{{
data: {{ delegate-to: '{delegate_to} }}
Copy link
Contributor

@janniks janniks Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be delegated-to like in the delegation map in Clarity? cc @friedger

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@janniks I was inspired by the delegate-stx parameter name and the database column of the pox3_events table

But your suggestion makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created new issue #4246

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good point. Might make sense in that case to keep named the same. Not sure 🤔

}}
"#,
delegate_to = opt
.data
.map(|boxed_value| *boxed_value)
.unwrap()
.expect_tuple()
.get("delegated-to")
.unwrap()
)
} else {
"{data: {unimplemented: true}}".into()
}
}
_ => "{data: {unimplemented: true}}".into(),
}
}

/// Synthesize an events data tuple to return on the successful execution of a pox-2 or pox-3 stacking
/// Synthesize an events data tuple to return on the successful execution of a pox-2 or pox-3 or pox-4 stacking
/// function. It runs a series of Clarity queries against the PoX contract's data space (including
/// calling PoX functions).
pub fn synthesize_pox_event_info(
Expand All @@ -348,6 +374,7 @@ pub fn synthesize_pox_event_info(
sender_opt: Option<&PrincipalData>,
function_name: &str,
args: &[Value],
response: &ResponseData,
) -> Result<Option<Value>, ClarityError> {
let sender = match sender_opt {
Some(sender) => sender,
Expand All @@ -362,7 +389,8 @@ pub fn synthesize_pox_event_info(
| "delegate-stack-extend"
| "stack-increase"
| "delegate-stack-increase"
| "delegate-stx" => Some(create_event_info_stack_or_delegate_code(
| "delegate-stx"
| "revoke-delegate-stx" => Some(create_event_info_stack_or_delegate_code(
sender,
function_name,
args,
Expand All @@ -377,12 +405,12 @@ pub fn synthesize_pox_event_info(
None => return Ok(None),
};

let data_snippet = create_event_info_data_code(function_name, args);
let data_snippet = create_event_info_data_code(function_name, args, response);

test_debug!("Evaluate snippet:\n{}", &code_snippet);
test_debug!("Evaluate data code:\n{}", &data_snippet);

let pox_2_contract = global_context
let pox_contract = global_context
.database
.get_contract(contract_id)
.expect("FATAL: could not load PoX contract metadata");
Expand All @@ -391,7 +419,7 @@ pub fn synthesize_pox_event_info(
.special_cc_handler_execute_read_only(
sender.clone(),
None,
pox_2_contract.contract_context,
pox_contract.contract_context,
|env| {
let base_event_info = env
.eval_read_only_with_rules(contract_id, &code_snippet, ASTRules::PrecheckSize)
Expand Down
1 change: 1 addition & 0 deletions pox-locking/src/pox_2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ pub fn handle_contract_call(
sender_opt,
function_name,
args,
response,
) {
Ok(Some(event_info)) => Some(event_info),
Ok(None) => None,
Expand Down
1 change: 1 addition & 0 deletions pox-locking/src/pox_3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ pub fn handle_contract_call(
sender_opt,
function_name,
args,
response,
) {
Ok(Some(event_info)) => Some(event_info),
Ok(None) => None,
Expand Down
1 change: 1 addition & 0 deletions pox-locking/src/pox_4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ pub fn handle_contract_call(
sender_opt,
function_name,
args,
response,
) {
Ok(Some(event_info)) => Some(event_info),
Ok(None) => None,
Expand Down
44 changes: 44 additions & 0 deletions stackslib/src/chainstate/stacks/boot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1792,6 +1792,50 @@ pub mod test {
make_tx(key, nonce, 0, payload)
}

pub fn make_pox_4_delegate_stx(
key: &StacksPrivateKey,
nonce: u64,
amount: u128,
delegate_to: PrincipalData,
until_burn_ht: Option<u128>,
pox_addr: Option<PoxAddress>,
) -> StacksTransaction {
let payload = TransactionPayload::new_contract_call(
boot_code_test_addr(),
POX_4_NAME,
"delegate-stx",
vec![
Value::UInt(amount),
Value::Principal(delegate_to.clone()),
match until_burn_ht {
Some(burn_ht) => Value::some(Value::UInt(burn_ht)).unwrap(),
None => Value::none(),
},
match pox_addr {
Some(addr) => {
Value::some(Value::Tuple(addr.as_clarity_tuple().unwrap())).unwrap()
}
None => Value::none(),
},
],
)
.unwrap();

make_tx(key, nonce, 0, payload)
}

pub fn make_pox_4_revoke_delegate_stx(key: &StacksPrivateKey, nonce: u64) -> StacksTransaction {
let payload = TransactionPayload::new_contract_call(
boot_code_test_addr(),
POX_4_NAME,
"revoke-delegate-stx",
vec![],
)
.unwrap();

make_tx(key, nonce, 0, payload)
}

fn make_tx(
key: &StacksPrivateKey,
nonce: u64,
Expand Down
10 changes: 8 additions & 2 deletions stackslib/src/chainstate/stacks/boot/pox-4.clar
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
(define-constant ERR_STACKING_NOT_DELEGATED 31)
(define-constant ERR_INVALID_SIGNER_KEY 32)
(define-constant ERR_REUSED_SIGNER_KEY 33)
(define-constant ERR_DELEGATION_ALREADY_REVOKED 34)

;; Valid values for burnchain address versions.
;; These first four correspond to address hash modes in Stacks 2.1,
Expand Down Expand Up @@ -600,12 +601,17 @@
;; return the lock-up information, so the node can actually carry out the lock.
(ok { stacker: tx-sender, lock-amount: amount-ustx, signer-key: signer-key, unlock-burn-height: (reward-cycle-to-burn-height (+ first-reward-cycle lock-period)) }))))

;; Revokes the delegation to the current stacking pool.
;; New in pox-4: Fails if the delegation was already revoked.
;; Returns the last delegation state.
(define-public (revoke-delegate-stx)
(begin
(let ((last-delegation-state (get-check-delegation tx-sender)))
;; must be called directly by the tx-sender or by an allowed contract-caller
(asserts! (check-caller-allowed)
(err ERR_STACKING_PERMISSION_DENIED))
(ok (map-delete delegation-state { stacker: tx-sender }))))
(asserts! (is-some last-delegation-state) (err ERR_DELEGATION_ALREADY_REVOKED))
(asserts! (map-delete delegation-state { stacker: tx-sender }) (err ERR_DELEGATION_ALREADY_REVOKED))
(ok last-delegation-state)))

;; Delegate to `delegate-to` the ability to stack from a given address.
;; This method _does not_ lock the funds, rather, it allows the delegate
Expand Down
2 changes: 1 addition & 1 deletion stackslib/src/chainstate/stacks/boot/pox_3_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3106,7 +3106,7 @@ fn pox_3_getters() {
tip.block_height,
);

// bob deleates to charlie
// bob delegates to charlie
let bob_delegate_tx = make_pox_3_contract_call(
&bob,
0,
Expand Down
158 changes: 158 additions & 0 deletions stackslib/src/chainstate/stacks/boot/pox_4_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,164 @@ fn pox_3_unlocks() {
}
}

// test that revoke-delegate-stx calls emit an event and
// test that revoke-delegate-stx is only successfull if user has delegated.
#[test]
fn pox_4_revoke_delegate_stx_events() {
// Config for this test
let (epochs, pox_constants) = make_test_epochs_pox();

let mut burnchain = Burnchain::default_unittest(
0,
&BurnchainHeaderHash::from_hex(BITCOIN_REGTEST_FIRST_BLOCK_HASH).unwrap(),
);
burnchain.pox_constants = pox_constants.clone();

let observer = TestEventObserver::new();

let (mut peer, mut keys) = instantiate_pox_peer_with_epoch(
&burnchain,
function_name!(),
Some(epochs.clone()),
Some(&observer),
);

assert_eq!(burnchain.pox_constants.reward_slots(), 6);
let mut coinbase_nonce = 0;
let mut latest_block;

// alice
let alice = keys.pop().unwrap();
let alice_address = key_to_stacks_addr(&alice);
let alice_principal = PrincipalData::from(alice_address.clone());

// bob
let bob = keys.pop().unwrap();
let bob_address = key_to_stacks_addr(&bob);
let bob_principal = PrincipalData::from(bob_address.clone());
let bob_pox_addr = make_pox_addr(AddressHashMode::SerializeP2PKH, bob_address.bytes.clone());

let mut alice_nonce = 0;

// Advance into pox4
let target_height = burnchain.pox_constants.pox_4_activation_height;
// produce blocks until the first reward phase that everyone should be in
while get_tip(peer.sortdb.as_ref()).block_height < u64::from(target_height) {
latest_block = peer.tenure_with_txs(&[], &mut coinbase_nonce);
}

info!(
"Block height: {}",
get_tip(peer.sortdb.as_ref()).block_height
);

// alice delegates 100 STX to Bob
let alice_delegation_amount = 100_000_000;
let alice_delegate = make_pox_4_delegate_stx(
&alice,
alice_nonce,
alice_delegation_amount,
bob_principal,
None,
None,
);
let alice_delegate_nonce = alice_nonce;
alice_nonce += 1;

let alice_revoke = make_pox_4_revoke_delegate_stx(&alice, alice_nonce);
let alice_revoke_nonce = alice_nonce;
alice_nonce += 1;

let alice_revoke_2 = make_pox_4_revoke_delegate_stx(&alice, alice_nonce);
let alice_revoke_2_nonce = alice_nonce;
alice_nonce += 1;

peer.tenure_with_txs(
&[alice_delegate, alice_revoke, alice_revoke_2],
&mut coinbase_nonce,
);

// check delegate with expiry

let target_height = get_tip(peer.sortdb.as_ref()).block_height + 10;
let alice_delegate_2 = make_pox_4_delegate_stx(
&alice,
alice_nonce,
alice_delegation_amount,
PrincipalData::from(bob_address.clone()),
Some(target_height as u128),
None,
);
let alice_delegate_2_nonce = alice_nonce;
alice_nonce += 1;

peer.tenure_with_txs(&[alice_delegate_2], &mut coinbase_nonce);

// produce blocks until delegation expired
while get_tip(peer.sortdb.as_ref()).block_height <= u64::from(target_height) {
peer.tenure_with_txs(&[], &mut coinbase_nonce);
}

let alice_revoke_3 = make_pox_4_revoke_delegate_stx(&alice, alice_nonce);
let alice_revoke_3_nonce = alice_nonce;
alice_nonce += 1;

peer.tenure_with_txs(&[alice_revoke_3], &mut coinbase_nonce);

let blocks = observer.get_blocks();
let mut alice_txs = HashMap::new();

for b in blocks.into_iter() {
for r in b.receipts.into_iter() {
if let TransactionOrigin::Stacks(ref t) = r.transaction {
let addr = t.auth.origin().address_testnet();
if addr == alice_address {
alice_txs.insert(t.auth.get_origin_nonce(), r);
}
}
}
}
assert_eq!(alice_txs.len() as u64, 5);

// check event for first revoke delegation tx
let revoke_delegation_tx_events = &alice_txs.get(&alice_revoke_nonce).unwrap().clone().events;
assert_eq!(revoke_delegation_tx_events.len() as u64, 1);
let revoke_delegation_tx_event = &revoke_delegation_tx_events[0];
let revoke_delegate_stx_op_data = HashMap::from([(
"delegate-to",
Value::Principal(PrincipalData::from(bob_address.clone())),
)]);
let common_data = PoxPrintFields {
op_name: "revoke-delegate-stx".to_string(),
stacker: alice_principal.clone().into(),
balance: Value::UInt(10240000000000),
locked: Value::UInt(0),
burnchain_unlock_height: Value::UInt(0),
};
check_pox_print_event(
revoke_delegation_tx_event,
common_data,
revoke_delegate_stx_op_data,
);

// second revoke transaction should fail
assert_eq!(
&alice_txs[&alice_revoke_2_nonce].result.to_string(),
"(err 34)"
);

// second delegate transaction should succeed
assert_eq!(
&alice_txs[&alice_delegate_2_nonce].result.to_string(),
"(ok true)"
);
// third revoke transaction should fail
assert_eq!(
&alice_txs[&alice_revoke_3_nonce].result.to_string(),
"(err 34)"
);
}

fn assert_latest_was_burn(peer: &mut TestPeer) {
let tip = get_tip(peer.sortdb.as_ref());
let tip_index_block = tip.get_canonical_stacks_block_id();
Expand Down