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

LSP error with certain tuple #1382

Closed
hstove opened this issue Mar 15, 2024 · 9 comments
Closed

LSP error with certain tuple #1382

hstove opened this issue Mar 15, 2024 · 9 comments
Assignees

Comments

@hstove
Copy link
Contributor

hstove commented Mar 15, 2024

As part of Clarigen I have a step where I basically evaluate all constants and variables to get their default values. The way I do this is by re-deploying the contract and returning a tuple with the key/value pairs.

I've been using this with some of the boot contracts for Nakamoto, and I ran into a peculiar issue with the signers-voting.clar contract. The LSP highlights the bottom tuple (created programmatically by Clarigen) and shows the error invalid tuple syntax, expecting list of pair. I also get an error when trying to deploy this contract using clarinet-sdk.

EDIT: the error stems from the length of the lists. I've found that if the lists are both of length 2569, there is no error. Any higher will cause an error.

It seems to relate to the two bottom stackerdb-signer-slots-N lines. If I comment either of them out, the error goes away. If I rename one or both of them, it the error stays. If I rename one or both of them (including the variable name), the errors stays. If I wrap that tuple inside a function, the error stays. It doesn't appear that there are any funky characters in this, but I might be wrong. I've tried to debug if I'm missing something obvious here in the generated code, but I haven't found anything.

To reproduce: in any Clarinet project, add a contract to your manifest, and paste in this code:

(define-data-var last-set-cycle uint u0)
(define-data-var stackerdb-signer-slots-0 (list 4000 { signer: principal, num-slots: uint }) (list))
(define-data-var stackerdb-signer-slots-1 (list 4000 { signer: principal, num-slots: uint }) (list))
(define-map cycle-set-height uint uint)
(define-constant MAX_WRITES u4294967295)
(define-constant CHUNK_SIZE (* u2 u1024 u1024))
(define-constant ERR_NO_SUCH_PAGE u1)
(define-constant ERR_CYCLE_NOT_SET u2)

(define-map cycle-signer-set uint (list 4000 { signer: principal, weight: uint }))

;; Called internally by the Stacks node.
;; Stores the stackerdb signer slots for a given reward cycle.
;; Since there is one stackerdb per signer message, the `num-slots` field will always be u1.
(define-private (stackerdb-set-signer-slots 
                   (signer-slots (list 4000 { signer: principal, num-slots: uint }))
                   (reward-cycle uint)
                   (set-at-height uint))
	(let ((cycle-mod (mod reward-cycle u2)))
        (map-set cycle-set-height reward-cycle set-at-height)
        (var-set last-set-cycle reward-cycle)
        (if (is-eq cycle-mod u0)
            (ok (var-set stackerdb-signer-slots-0 signer-slots))
            (ok (var-set stackerdb-signer-slots-1 signer-slots)))))

;; Called internally by the Stacks node.
;; Sets the list of signers and weights for a given reward cycle.
(define-private (set-signers
                 (reward-cycle uint)
                 (signers (list 4000 { signer: principal, weight: uint })))
     (begin
      (asserts! (is-eq (var-get last-set-cycle) reward-cycle) (err ERR_CYCLE_NOT_SET))
      (ok (map-set cycle-signer-set reward-cycle signers))))

;; Get the list of signers and weights for a given reward cycle.
(define-read-only (get-signers (cycle uint))
     (map-get? cycle-signer-set cycle))

;; called by .signers-(0|1)-xxx contracts to get the signers for their respective signing sets
(define-read-only (stackerdb-get-signer-slots-page (page uint))
    (if (is-eq page u0)     (ok (var-get stackerdb-signer-slots-0))
        (if (is-eq page u1)  (ok (var-get stackerdb-signer-slots-1))
            (err ERR_NO_SUCH_PAGE))))

;; Get a signer's signing weight by a given index.
;; Used by other contracts (e.g. the voting contract) 
(define-read-only (get-signer-by-index (cycle uint) (signer-index uint))
	(ok (element-at (unwrap! (map-get? cycle-signer-set cycle) (err ERR_CYCLE_NOT_SET)) signer-index)))

;; called by .signers-(0|1)-xxx contracts
;; NOTE: the node may ignore `write-freq`, since not all stackerdbs will be needed at a given time
(define-read-only (stackerdb-get-config)
	(ok
		{ chunk-size: CHUNK_SIZE,
		  write-freq: u0, 
		  max-writes: MAX_WRITES,
		  max-neighbors: u32,
		  hint-replicas: (list ) }
	))

(define-read-only (get-last-set-cycle)
	(ok (var-get last-set-cycle)))

{
    CHUNK_SIZE: CHUNK_SIZE,
    ERR_CYCLE_NOT_SET: ERR_CYCLE_NOT_SET,
    ERR_NO_SUCH_PAGE: ERR_NO_SUCH_PAGE,
    MAX_WRITES: MAX_WRITES,
    last-set-cycle: (var-get last-set-cycle),
    stackerdb-signer-slots-0: (var-get stackerdb-signer-slots-0),
    stackerdb-signer-slots-1: (var-get stackerdb-signer-slots-1),
}
@hstove
Copy link
Contributor Author

hstove commented Mar 15, 2024

Ah, I figured out the problem - it's due to the length of the lists. I'll update my description.

@MarvinJanssen
Copy link

You can use getContractAST() and then walk through the symbols to find all constants and data-vars. Then you do not have to modify and redeploy the contracts, avoiding the risk of running into max length issues. Here is a basic example that finds a constant stx-bootstrap-amount and extracts the value. (uint in this case.)

async function findStxBootstrapAmount() {
	const simnet = await initSimnet(manifestFile);
	let boot: any = null;
	try {
		boot = simnet.getContractAST('boot');
	}
	catch (error) {
		throw new Error(`Failed to read boot contract`);
	}
	return findStxBootstrapAmountAtom(boot.expressions);
}

function findStxBootstrapAmountAtom(items: any[]): bigint | null {
	for (let i = 0; i < items.length; ++i) {
		const item = items[i];
		if (item.expr?.List?.length === 3) {
			let [a, b, c] = item.expr.List;
			if (a.expr?.Atom === "define-constant" && b.expr?.Atom === "stx-bootstrap-amount")
				return c.expr.LiteralValue.UInt;
		}
		else if (item.expr?.List) {
			const result: any = findStxBootstrapAmountAtom(item.expr.List);
			if (result !== null)
				return result;
		}
	}
	return null;
}

@hugocaillard
Copy link
Collaborator

Hey @hstove
I think with this doe, you reach some sort of maximum bytes size for the tuple

(define-data-var stackerdb-signer-slots-0 (list 2570 { signer: principal, num-slots: uint }) (list))
(define-data-var stackerdb-signer-slots-1 (list 2570 { signer: principal, num-slots: uint }) (list))
{
    stackerdb-signer-slots-0: (var-get stackerdb-signer-slots-0),
    stackerdb-signer-slots-1: (var-get stackerdb-signer-slots-1),
}

What do you think about @MarvinJanssen solution?

@hstove
Copy link
Contributor Author

hstove commented Mar 18, 2024

@MarvinJanssen yeah that works for the majority of cases, but not with dynamic / code based things, like:

(define-constant FIVE_STX (u1 * u1000000))
(define-data-var fee-rate uint (contract-call? .fee get-rate) u1)

There are definitely workarounds, and I think this also points to a better solution of not using a single tuple for all variables. It could be the same approach but with a single read-only fn to get each variable individually, etc.

@hugocaillard
Copy link
Collaborator

@hstove
You can also use simnet.getDataVar, you should get the right value. I could try to see if I can have something similar for constants

@hstove
Copy link
Contributor Author

hstove commented Mar 18, 2024

Ah, true! I wrote this code before @clarinet/sdk but that's much much better.

@hstove
Copy link
Contributor Author

hstove commented Mar 18, 2024

Closing this - maybe the LSP error should be better, but there isn't a bug around whether this should compile.

@hstove hstove closed this as completed Mar 18, 2024
@hugocaillard
Copy link
Collaborator

Thanks @hstove and @MarvinJanssen
Feel free to open a feature request issue for getConstant 👍
(Although I'm not sure I can provide that out of the box)

@MarvinJanssen
Copy link

I didn't know about simnet.getDataVar, pretty cool!

There are definitely workarounds, and I think this also points to a better solution of not using a single tuple for all variables. It could be the same approach but with a single read-only fn to get each variable individually, etc.

Right. I guess you could turn the AST back into Clarity code and evaluate it if you detect that it is not a simple atomic value, but that sounds a little tricky.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

No branches or pull requests

3 participants