Skip to content

Trait Aliases can be Overridden by Local Trait Definition #3215

@sskeirik

Description

@sskeirik

Describe the bug
The (use-trait trait-alias contract.trait-name) statement makes trait-name available locally under name trait-alias.

However, if this contract also contains a (define-trait trait-name (...)), then trait-alias will be overridden to point to the current contract's version of trait-name instead of the trait definition contained in the contract pointed to by use-trait.

Let's see an example.
Clarity will accept this first contract:

a-trait.clar

(define-trait a (
  (do-it () (response bool bool))
))

However, the contract below is rejected:

use-a-trait-1.clar

(use-trait a-alias 'SP8CW062DS1XAZJJXWKSM9EMMDD51BRVFMY8MBX6.a-trait.a)

(define-trait a (
  (do-that () (response bool bool))
))

(define-public (call-do-it (a-contract <a-alias>))
  (contract-call? a-contract do-it)
)

It returns the error:

**{"error":{"initialization":"TraitMethodUnknown(\"a\", \"do-it\")
Near:
[SymbolicExpression { expr: List([SymbolicExpression { expr: Atom(ClarityName(\"contract-call?\")), id: 13, span: Span { start_line: 4, start_column: 4, end_line: 4, end_column: 17 } }, SymbolicExpression { expr: Atom(ClarityName(\"a-contract\")), id: 14, span: Span { start_line: 4, start_column: 19, end_line: 4, end_column: 28 } }, SymbolicExpression { expr: Atom(ClarityName(\"do-it\")), id: 15, span: Span { start_line: 4, start_column: 30, end_line: 4, end_column: 34 } }]), id: 12, span: Span { start_line: 4, start_column: 3, end_line: 4, end_column: 35 } }]"}}**

On the other hand, the following contract is accepted:

use-a-trait-2.clar

(use-trait a-alias 'SP8CW062DS1XAZJJXWKSM9EMMDD51BRVFMY8MBX6.a-trait.a)

(define-trait a (
  (do-that () (response bool bool))
))

;; trait name a points to current version of a trait; seems OK
(define-public (call-do-that-2 (a-contract <a>))
  (contract-call? a-contract do-that)
)

;; trait alias from use statement _also_ points to current version of a trait; seems wrong
(define-public (call-do-that (a-contract <a-alias>))
  (contract-call? a-contract do-that)
)

Steps To Reproduce
If the contract files are named as above, then the CLI invocation to test would be:

clarity-cli initialize initial-allocations.json vm-state.db
clarity-cli launch SP8CW062DS1XAZJJXWKSM9EMMDD51BRVFMY8MBX6.a-trait a-trait.clar vm-state.db
clarity-cli launch SP8CW062DS1XAZJJXWKSM9EMMDD51BRVFMY8MBX6.use-a-trait-1 use-a-trait-1.clar vm-state.db
clarity-cli launch SP8CW062DS1XAZJJXWKSM9EMMDD51BRVFMY8MBX6.use-a-trait-2 use-a-trait-2.clar vm-state.db

Expected behavior
So, firstly, I'm not sure if it makes sense to allow a contract to do:

(use-trait a-alias contract-name.a)
(define-trait a (...))

Since this makes the rules for transitive imports via use-trait a bit more complex (i.e. there is a kind of name shadowing in the transitive trait use process where clarity seems to frown upon name shadowing) since doing the following in a later contract:

(use-trait a-alias-2 SP8CW062DS1XAZJJXWKSM9EMMDD51BRVFMY8MBX6.use-a-trait-2.a)

will get me use-a-trait-2's version of trait a instead of the original.

However, if this is to be allowed, then I expect that all uses of a-alias inside the contract use-a-trait-1 and use-a-trait-2 should point to the trait definition imported by use-trait from a-trait and not to the local definition of trait a.

Environment (please complete the following information):

Additional context
N/A

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions