Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Boot-contracts compilation step on CI #432

Merged
merged 6 commits into from
Jul 3, 2024
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
18 changes: 18 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,24 @@ jobs:
cargo llvm-cov nextest \
--archive-file nextest-archive.tar.zst

compile-boot-contracts:
name: Compile boot-contracts
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./clar2wasm
steps:
- name: Checkout PR
uses: actions/checkout@v4

- name: Use Rust stable
uses: dtolnay/rust-toolchain@stable

- name: Compile boot-contracts
run: |
cargo build --release
bash ./scripts/boot-contracts-compile.sh

codecov:
name: Code Coverage
runs-on: ubuntu-latest
Expand Down
42 changes: 42 additions & 0 deletions clar2wasm/scripts/boot-contracts-compile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash

BOOT_CONTRACTS_PATH="./tests/contracts/boot-contracts"
CLAR2WASM_PATH="../target/release/clar2wasm"

declare -a boot_contracts=(
bns.clar
cost-voting.clar
costs-2-testnet.clar
costs-2.clar
costs-3.clar
costs.clar
genesis.clar
lockup.clar
pox-mainnet-prepared.clar
pox-testnet-prepared.clar
pox-2-mainnet-prepared.clar
pox-2-testnet-prepared.clar
pox-3-mainnet-prepared.clar
pox-3-testnet-prepared.clar
pox-4.clar
signers.clar
)

cat "${BOOT_CONTRACTS_PATH}/pox-mainnet.clar" "${BOOT_CONTRACTS_PATH}/pox.clar" >> "${BOOT_CONTRACTS_PATH}/pox-mainnet-prepared.clar"
cat "${BOOT_CONTRACTS_PATH}/pox-testnet.clar" "${BOOT_CONTRACTS_PATH}/pox.clar" >> "${BOOT_CONTRACTS_PATH}/pox-testnet-prepared.clar"
cat "${BOOT_CONTRACTS_PATH}/pox-mainnet.clar" "${BOOT_CONTRACTS_PATH}/pox-2.clar" >> "${BOOT_CONTRACTS_PATH}/pox-2-mainnet-prepared.clar"
cat "${BOOT_CONTRACTS_PATH}/pox-testnet.clar" "${BOOT_CONTRACTS_PATH}/pox-2.clar" >> "${BOOT_CONTRACTS_PATH}/pox-2-testnet-prepared.clar"
cat "${BOOT_CONTRACTS_PATH}/pox-mainnet.clar" "${BOOT_CONTRACTS_PATH}/pox-3.clar" >> "${BOOT_CONTRACTS_PATH}/pox-3-mainnet-prepared.clar"
cat "${BOOT_CONTRACTS_PATH}/pox-testnet.clar" "${BOOT_CONTRACTS_PATH}/pox-3.clar" >> "${BOOT_CONTRACTS_PATH}/pox-3-testnet-prepared.clar"

for contract in ${boot_contracts[@]}; do
echo "Compiling $contract file..."
"${CLAR2WASM_PATH}" "${BOOT_CONTRACTS_PATH}/$contract"

if [ $? == 0 ]; then
echo "Success"
else
echo "Failure while compiling $contract"
exit 1
fi
done
49 changes: 49 additions & 0 deletions clar2wasm/tests/boot_contracts_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::collections::HashMap;

use clar2wasm::compile;
use clar2wasm::datastore::{BurnDatastore, StacksConstants};
use clar2wasm::initialize::initialize_contract;
use clar2wasm::tools::execute;
use clar2wasm::wasm_utils::call_function;
use clarity::consts::CHAIN_ID_TESTNET;
use clarity::types::StacksEpochId;
use clarity::vm::contexts::{CallStack, GlobalContext};
use clarity::vm::contracts::Contract;
use clarity::vm::costs::LimitedCostTracker;
use clarity::vm::database::{ClarityDatabase, MemoryBackingStore};
use clarity::vm::errors::{CheckErrors, Error};
use clarity::vm::types::{
PrincipalData, QualifiedContractIdentifier, ResponseData, StandardPrincipalData,
};
use clarity::vm::{ClarityVersion, ContractContext, Value};

#[macro_use]
mod lib_tests;

//
// Boot contracts tests
//

// signers.clar

test_multi_contract_call_response!(
test_get_signer_by_index,
["boot-contracts/signers", "boot-contracts/signers-caller"],
"signers-caller",
"get-signer-by-index",
|response: ResponseData| {
assert!(!response.committed);
assert_eq!(*response.data, Value::UInt(2));
}
);

test_multi_contract_call_response!(
test_get_last_set_cycle,
["boot-contracts/signers", "boot-contracts/signers-caller"],
"signers-caller",
"get-last-set-cycle",
|response: ResponseData| {
assert!(response.committed);
assert_eq!(*response.data, Value::UInt(0));
}
);
261 changes: 261 additions & 0 deletions clar2wasm/tests/contracts/boot-contracts/cost-voting.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
;; The .cost-voting contract

;; error codes
(define-constant ERR_NO_SUCH_PROPOSAL 1)
(define-constant ERR_AMOUNT_NOT_POSITIVE 2)
(define-constant ERR_PROPOSAL_EXPIRED 3)
(define-constant ERR_VOTE_ENDED 4)
(define-constant ERR_INSUFFICIENT_FUNDS 5)
(define-constant ERR_FT_TRANSFER 6)
(define-constant ERR_STX_TRANSFER 7)
(define-constant ERR_VOTE_NOT_CONFIRMED 8)
(define-constant ERR_ALREADY_VETOED 9)
(define-constant ERR_NOT_LAST_MINER 10)
(define-constant ERR_INSUFFICIENT_VOTES 11)
(define-constant ERR_VETO_PERIOD_OVER 12)
(define-constant ERR_VETO_PERIOD_NOT_OVER 13)
(define-constant ERR_PROPOSAL_VETOED 14)
(define-constant ERR_PROPOSAL_CONFIRMED 15)
(define-constant ERR_FETCHING_BLOCK_INFO 16)
(define-constant ERR_TOO_MANY_CONFIRMED 17)
(define-constant ERR_UNREACHABLE 255)

(define-constant VOTE_LENGTH u2016)
(define-constant VETO_LENGTH u1008)
(define-constant REQUIRED_PERCENT_STX_VOTE u20)
(define-constant REQUIRED_VETOES u500)

(define-constant MAX_CONFIRMED_PER_BLOCK u10)

;; cost vote token
(define-fungible-token cost-vote-token)

;; proposal counters
(define-data-var proposal-count uint u0)
(define-data-var confirmed-proposal-count uint u0)

;; cost-function proposals
(define-map proposals
{ proposal-id: uint }
{
cost-function-contract: principal,
cost-function-name: (string-ascii 128),
function-contract: principal,
function-name: (string-ascii 128),
expiration-block-height: uint
}
)

;; vote confirmed cost-function proposals
(define-map vote-confirmed-proposals
{ proposal-id: uint }
{ expiration-block-height: uint }
)

;; miner confirmed cost-function proposals
(define-map confirmed-proposals
{ confirmed-id: uint }
{
function-contract: principal,
function-name: (string-ascii 128),
cost-function-contract: principal,
cost-function-name: (string-ascii 128),
confirmed-height: uint
}
)

;; limit the number of miner confirmed-proposals
;; that can be introduced per block
;; track the # of proposals confirmed at a given block-height
(define-map confirmed-count-at-block uint uint)

(define-map proposal-confirmed-id
{ proposal-id: uint }
{ confirmed-id: uint }
)

(define-map functions-to-confirmed-ids
{ function-contract: principal, function-name: (string-ascii 128) }
{ proposal-id: uint }
)

;; cost-function proposal votes
(define-map proposal-votes { proposal-id: uint } { votes: uint })

;; cost-function proposal vetos
(define-map proposal-vetos { proposal-id: uint } { vetos: uint })

;; proposal vetos per block
(define-map exercised-veto { proposal-id: uint, veto-height: uint } { vetoed: bool })

;; the number of votes a specific principal has committed to a proposal
(define-map principal-proposal-votes { address: principal, proposal-id: uint } { votes: uint })

;; getter for cost-function proposals
(define-read-only (get-proposal (proposal-id uint))
(map-get? proposals { proposal-id: proposal-id }))

;; getter for confirmed cost-function proposals
(define-read-only (get-confirmed-proposal (confirmed-id uint))
(map-get? confirmed-proposals { confirmed-id: confirmed-id }))

;; getter for cost-function proposal votes
(define-read-only (get-proposal-votes (proposal-id uint))
(get votes (map-get? proposal-votes { proposal-id: proposal-id })))

;; getter for cost-function proposal vetos
(define-read-only (get-proposal-vetos (proposal-id uint))
(get vetos (map-get? proposal-vetos { proposal-id: proposal-id })))

;; getter for cost-function proposal votes, for specific principal
(define-read-only (get-principal-votes (address principal) (proposal-id uint))
(get votes (map-get? principal-proposal-votes { address: address, proposal-id: proposal-id })))

;; Propose cost-functions
(define-public (submit-proposal (function-contract principal)
(function-name (string-ascii 128))
(cost-function-contract principal)
(cost-function-name (string-ascii 128)))
(begin
(map-insert proposals { proposal-id: (var-get proposal-count) }
{ cost-function-contract: cost-function-contract,
cost-function-name: cost-function-name,
function-contract: function-contract,
function-name: function-name,
expiration-block-height: (+ block-height VOTE_LENGTH) })
(map-insert proposal-votes { proposal-id: (var-get proposal-count) } { votes: u0 })
(var-set proposal-count (+ (var-get proposal-count) u1))
(ok (- (var-get proposal-count) u1))))

;; Vote on a proposal
(define-public (vote-proposal (proposal-id uint) (amount uint))
(let (
(expiration-block-height (get expiration-block-height (unwrap! (map-get? proposals {
proposal-id: proposal-id }) (err ERR_NO_SUCH_PROPOSAL))))
(cur-votes (default-to u0 (get votes (map-get? proposal-votes { proposal-id: proposal-id }))))
(cur-principal-votes (default-to u0 (get votes (map-get? principal-proposal-votes {
address: tx-sender,
proposal-id: proposal-id })))))

;; a vote must have a positive amount
(asserts! (> amount u0) (err ERR_AMOUNT_NOT_POSITIVE))

;; the vote must occur before the expiration
(asserts! (< block-height expiration-block-height) (err ERR_PROPOSAL_EXPIRED))

;; the proposal must not already be voter confirmed
(asserts! (is-none (map-get? vote-confirmed-proposals { proposal-id: proposal-id }))
(err ERR_VOTE_ENDED))

(unwrap! (stx-transfer? amount tx-sender (as-contract tx-sender)) (err ERR_INSUFFICIENT_FUNDS))
(unwrap! (ft-mint? cost-vote-token amount tx-sender) (err ERR_UNREACHABLE))

(map-set proposal-votes { proposal-id: proposal-id } { votes: (+ amount cur-votes) })
(map-set principal-proposal-votes { address: tx-sender, proposal-id: proposal-id}
{ votes: (+ amount cur-principal-votes)})
(ok true)))

;; Withdraw votes
(define-public (withdraw-votes (proposal-id uint) (amount uint))
(let (
(cur-votes (default-to u0 (get votes (map-get? proposal-votes { proposal-id: proposal-id }))))
(cur-principal-votes (default-to u0 (get votes (map-get? principal-proposal-votes {
address: tx-sender,
proposal-id: proposal-id }))))
(sender tx-sender))

(asserts! (> amount u0) (err ERR_AMOUNT_NOT_POSITIVE))
(asserts! (>= cur-principal-votes amount) (err ERR_INSUFFICIENT_FUNDS))

(unwrap! (as-contract (stx-transfer? amount tx-sender sender)) (err ERR_STX_TRANSFER))
(unwrap! (as-contract (ft-transfer? cost-vote-token amount sender tx-sender))
(err ERR_FT_TRANSFER))

(map-set proposal-votes { proposal-id: proposal-id } { votes: (- cur-votes amount) })
(map-set principal-proposal-votes { address: tx-sender, proposal-id: proposal-id }
{ votes: (- cur-principal-votes amount) })
(ok true)))

;; Miner veto
(define-public (veto (proposal-id uint))
(let (
(cur-vetos (default-to u0 (get vetos (map-get? proposal-vetos { proposal-id: proposal-id }))))
(expiration-block-height (get expiration-block-height (unwrap!
(map-get? vote-confirmed-proposals { proposal-id: proposal-id })
(err ERR_VOTE_NOT_CONFIRMED))))
(vetoed (default-to false (get vetoed (map-get? exercised-veto { proposal-id: proposal-id,
veto-height: block-height }))))
(last-miner (unwrap! (get-block-info? miner-address (- block-height u1))
(err ERR_FETCHING_BLOCK_INFO))))

;; a miner can only veto once per block
(asserts! (not vetoed) (err ERR_ALREADY_VETOED))

;; vetoes must be cast within the veto period
(asserts! (< block-height expiration-block-height) (err ERR_VETO_PERIOD_OVER))

;; a miner can only veto if they mined the previous block
(asserts! (is-eq contract-caller last-miner) (err ERR_NOT_LAST_MINER))

;; a veto cannot be cast if a proposal has already been miner confirmed
(asserts! (is-none (map-get? proposal-confirmed-id { proposal-id: proposal-id }))
(err ERR_PROPOSAL_CONFIRMED))

(map-set proposal-vetos { proposal-id: proposal-id } { vetos: (+ u1 cur-vetos) })
(map-set exercised-veto { proposal-id: proposal-id, veto-height: block-height }
{ vetoed: true })
(ok true)))

;; Confirm proposal has reached required vote count
(define-public (confirm-votes (proposal-id uint))
(let (
(votes (default-to u0 (get votes (map-get? proposal-votes { proposal-id: proposal-id }))))
(proposal (unwrap! (map-get? proposals { proposal-id: proposal-id }) (err ERR_NO_SUCH_PROPOSAL)))
(confirmed-count (var-get confirmed-proposal-count))
(expiration-block-height (get expiration-block-height proposal)))

;; confirmation fails if invoked after proposal has expired
(asserts! (< block-height expiration-block-height) (err ERR_PROPOSAL_EXPIRED))

;; confirmation fails if the required threshold of votes is not met
(asserts! (>= (/ (* votes u100) stx-liquid-supply) REQUIRED_PERCENT_STX_VOTE)
(err ERR_INSUFFICIENT_VOTES))

(map-insert vote-confirmed-proposals { proposal-id: proposal-id }
{ expiration-block-height: (+ VETO_LENGTH block-height) })

(ok true)))

;; Confirm proposal hasn't been vetoed
(define-public (confirm-miners (proposal-id uint))
(let ((vetos (default-to u0 (get vetos (map-get? proposal-vetos { proposal-id: proposal-id }))))
(vote-confirmed-proposal (unwrap! (map-get? vote-confirmed-proposals
{ proposal-id: proposal-id }) (err ERR_NO_SUCH_PROPOSAL)))
(proposal (unwrap! (map-get? proposals { proposal-id: proposal-id })
(err ERR_NO_SUCH_PROPOSAL)))
(confirmed-count (var-get confirmed-proposal-count))
(expiration-block-height (get expiration-block-height vote-confirmed-proposal))
(confirmed-this-block (default-to u0 (map-get? confirmed-count-at-block block-height))))

;; have we already confirmed too many proposals in this block
(asserts! (< confirmed-this-block MAX_CONFIRMED_PER_BLOCK) (err ERR_TOO_MANY_CONFIRMED))
(map-set confirmed-count-at-block block-height (+ u1 confirmed-this-block))

;; miner confirmation will fail if invoked before the expiration
(asserts! (>= block-height expiration-block-height) (err ERR_VETO_PERIOD_NOT_OVER))

;; miner confirmation will fail if there are enough vetos
(asserts! (< vetos REQUIRED_VETOES) (err ERR_PROPOSAL_VETOED))

(map-insert confirmed-proposals { confirmed-id: confirmed-count }
{
function-contract: (get function-contract proposal),
function-name: (get function-name proposal),
cost-function-contract: (get cost-function-contract proposal),
cost-function-name: (get cost-function-name proposal),
confirmed-height: block-height
})

(map-insert proposal-confirmed-id { proposal-id: proposal-id } { confirmed-id: confirmed-count })
(var-set confirmed-proposal-count (+ confirmed-count u1))
(ok true)))
Loading
Loading