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

Trait implementation #1246

Merged
merged 98 commits into from Feb 21, 2020
Merged
Show file tree
Hide file tree
Changes from 95 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
6270d26
Ability to parse generics enclosed in <>
lgalabru Jan 16, 2020
3083d38
Simplify parsing approach
lgalabru Jan 16, 2020
5ab6aee
Iteration on parser
lgalabru Jan 17, 2020
562780b
Add new class or errors, traits related
lgalabru Jan 17, 2020
7d73ee8
Revisit token's denominations in parser
lgalabru Jan 22, 2020
26ca32d
Parse sugared/fully qualified field identifier
lgalabru Jan 22, 2020
1ae60fa
Clean dead code, close loop with parsing, add basic tests
lgalabru Jan 22, 2020
c2dbb00
Introduce trait_checker pass
lgalabru Jan 22, 2020
c2348c3
Streamline sugared denominations
lgalabru Jan 22, 2020
11edfb8
Clarify variable name
lgalabru Jan 23, 2020
9265656
WIP
lgalabru Jan 29, 2020
5f102b5
Merge branch 'develop' into feature/traits
lgalabru Jan 29, 2020
46692d5
First execution working
lgalabru Jan 29, 2020
24f7f7e
Clean dead code
lgalabru Jan 29, 2020
38d9019
Refactoring field parsing
lgalabru Jan 29, 2020
85cd825
Improve error type
lgalabru Jan 29, 2020
d654433
Resolve todos - mainly refactoring
lgalabru Jan 29, 2020
2fe4036
Draft implementation of principal-of, resolve some todos
lgalabru Jan 30, 2020
47bf3bb
Better error type
lgalabru Jan 30, 2020
5e46a74
Fix impl-trait checking
lgalabru Jan 31, 2020
91a7f7b
Resolve read-only checks
lgalabru Jan 31, 2020
6e7751c
Add additional type checks on principal-of
lgalabru Jan 31, 2020
3e72a06
Add comments
lgalabru Jan 31, 2020
2e391d3
Get rid of the (unwanted) breadth traversal
lgalabru Jan 31, 2020
f66beed
Update definition sorter to support trait references
lgalabru Jan 31, 2020
215f649
Revisit impl-trait signature
lgalabru Jan 31, 2020
7f1b946
Strip dead code
lgalabru Jan 31, 2020
984eacb
Refactor: CallablePrincipalType -> TraitReferenceType
lgalabru Jan 31, 2020
91d133f
Improve consistency in naming
lgalabru Jan 31, 2020
2ca5fc5
Resolve outstanding todos
lgalabru Jan 31, 2020
7a7af97
Add documentation
lgalabru Jan 31, 2020
9e07489
Fix definition sorter
lgalabru Feb 2, 2020
d178c71
Refactor trait checker: pre-type-check / post-type-check
lgalabru Feb 2, 2020
fb3ee48
Revisit runtime checks approach
lgalabru Feb 2, 2020
f8ccd6c
Cascade changes in tests
lgalabru Feb 2, 2020
8b1a31a
Fix trait reference instance lookup
lgalabru Feb 2, 2020
b486f29
Strip logs from an earlier caveman debugging session
lgalabru Feb 2, 2020
51eb822
Add some tests covering checks at runtime
lgalabru Feb 2, 2020
e8691fe
Add some tests and fixes to the analysis
lgalabru Feb 3, 2020
9bfc480
Resolve todos, cleaning unwraps
lgalabru Feb 3, 2020
0d92ed2
Remove log
lgalabru Feb 3, 2020
29fa0bd
Remove dev contracts
lgalabru Feb 3, 2020
247c555
Fix typo in doc
lgalabru Feb 3, 2020
0da028c
Preclude trait reference shadowing
lgalabru Feb 3, 2020
e9df959
Add more tests
lgalabru Feb 3, 2020
87bcbcc
Add more tests, check ACL when dynamic dispatching
lgalabru Feb 3, 2020
35278b5
TraitReference moved to SymbolicExpression level (instead of Value)
lgalabru Feb 3, 2020
afef62e
Field moved to SymbolicExpression level (instead of Value)
lgalabru Feb 3, 2020
8faade5
Fix and test recursion bug
lgalabru Feb 3, 2020
64c4024
Should rely on prev type_check_define_function impl
lgalabru Feb 4, 2020
b6c9270
Fix indentation
lgalabru Feb 4, 2020
c11571d
Fix whitespaces
lgalabru Feb 4, 2020
69d8443
Revisit pre_type_check trait checker approach
lgalabru Feb 4, 2020
f6e62f9
Revert test fix
lgalabru Feb 4, 2020
b0628be
Do not consider trait's functions for sorting definitions
lgalabru Feb 4, 2020
260873a
Clarify comment
lgalabru Feb 4, 2020
e97bc2f
Add tests covering read-write compatibility
lgalabru Feb 4, 2020
7e00701
Resolve todos
lgalabru Feb 4, 2020
aa5ac71
Merge branch 'develop' into feature/traits
lgalabru Feb 4, 2020
d3a2e6c
Merge branch 'master' into feature/traits
lgalabru Feb 10, 2020
9ef76b8
Fix typo
lgalabru Feb 11, 2020
e075419
Add test case
lgalabru Feb 11, 2020
b676529
Resolve todo
lgalabru Feb 11, 2020
01400b7
Fix trait collisions
lgalabru Feb 11, 2020
c3ab40f
Fix typo
lgalabru Feb 11, 2020
6abba93
Disable intra-contract contract-call?
lgalabru Feb 12, 2020
fe5fb0c
Update SIP002
lgalabru Feb 12, 2020
cbb8554
Strip "init-factorial" messages
lgalabru Feb 12, 2020
4a85982
Merge branch 'master' into feature/traits
lgalabru Feb 13, 2020
af4cf66
Strip undesired condition
lgalabru Feb 13, 2020
a198bd1
Introducing new error type, adding test case
lgalabru Feb 13, 2020
993ac71
TraitReference should hold a trait_identifier (instead of alias)
lgalabru Feb 18, 2020
541a02d
Migrate DefinitionSorter, from analysis to build_ast phase
lgalabru Feb 18, 2020
24186fd
Upgrade PreSymbolicRepresentation structs
lgalabru Feb 18, 2020
8dbc4a2
Simplify define handler
lgalabru Feb 18, 2020
2d1e11b
Strip dead code in evaluation, add / update tests
lgalabru Feb 18, 2020
52382bd
Re-model the AST module / phase
lgalabru Feb 19, 2020
aec511f
Re-model the Analysis module / phase
lgalabru Feb 19, 2020
6287ee8
Re-write test
lgalabru Feb 19, 2020
d0b6dea
Refactor TraitsResolver
lgalabru Feb 19, 2020
3965261
Add more tests
lgalabru Feb 19, 2020
384af24
Introduce probe_for_dependencies_in_define_args, add test case
lgalabru Feb 19, 2020
53ca735
Refactor unions of matching cases
lgalabru Feb 19, 2020
497ff3a
Strip trailing whitespaces
lgalabru Feb 19, 2020
9fe4366
Use MAX_STRING_LEN instead of hard-coded 64 for trait-name lenght
lgalabru Feb 19, 2020
be3a249
Fix typo
lgalabru Feb 19, 2020
fee30e8
Remove log
lgalabru Feb 19, 2020
533edd9
Add test cases covering traits with multiple methods
lgalabru Feb 19, 2020
c9f524a
Strip (principal-of ...)
lgalabru Feb 21, 2020
058549a
DRY-ing duplicated logic
lgalabru Feb 21, 2020
3f49808
Strictly forbid dynamic dispatch from readonly contexts
lgalabru Feb 21, 2020
b214983
Check args count
lgalabru Feb 21, 2020
d409a83
Implement additional tests
lgalabru Feb 21, 2020
46f7ad3
Fix warnings
lgalabru Feb 21, 2020
1a390cf
Lazy wrapping trait_reference_id in lookup
lgalabru Feb 21, 2020
4d57ca8
Refactor func's name
lgalabru Feb 21, 2020
ec76b69
Fix typo
lgalabru Feb 21, 2020
0cc0da6
Add test case for definition sorter
lgalabru Feb 21, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
101 changes: 69 additions & 32 deletions sip/sip-002-smart-contract-language.md
Expand Up @@ -98,48 +98,85 @@ any type.
## Inter-Contract Calls

A smart contract may call functions from other smart contracts using a
`(contract-call!)` function. This function accepts a function name and
the smart contract's _identifier_ as input. A smart contract's
identifier is a hash of the smart contract's definition, represented
as a Stacks address with a specific "smart contract" version byte. The
smart contract identifier is a _principal_.
`(contract-call?)` function.

For example, to call the function `register-name` in a smart contract,
you would use:
This function returns a Response type result-- the return value of the called smart
contract function. Note that if a called smart contract returns an
`err` type, it is guaranteed to not alter any smart contract state
whatsoever. Of course, any transaction fees paid for the execution
of that function will not be returned.

We distinguish 2 different types of `contract-call?`:

* Static dispatch: the callee is a known, invariant contract available
on-chain when the caller contract is being deployed. In this case, the
callee's principal is provided as first argument, followed by the name
of the method and its arguments:

```scheme
(contract-call!
'SC3H92H297DX3YDPFHZGH90G8Z4NPH4VE8E83YWAQ
'register-name
(contract-call?
'SC3H92H297DX3YDPFHZGH90G8Z4NPH4VE8E83YWAQ.registrar
register-name
name-to-register)
```

This function returns a boolean-- the return value of the called smart
contract function. Note that if a called smart contract returns an
`err` type, it is guaranteed to not alter any smart contract state
whatsoever. Of course, any transaction fees paid for the execution
of that function will not be returned.
This approach must always be preferred, when adequate.
It makes static analysis easier, and eliminates the
potential for reentrancy bugs when the contracts are
being published (versus when being used).

* Dynamic dispatch: the callee is passed as an argument, and typed
as a trait reference (<A>).

```scheme
(define-public (swap (token-a <can-transfer-tokens>)
(amount-a uint)
(owner-a principal)
(token-b <can-transfer-tokens>)
(amount-b uint)
(owner-b principal)))
(begin
(unwrap! (contract-call? token-a transfer-from? owner-a owner-b amount-a))
(unwrap! (contract-call? token-b transfer-from? owner-b owner-a amount-b))))
```

Traits can either be locally defined:

```scheme
(define-trait can-transfer-tokens (
(transfer-from? (principal principal uint) (response uint)))
```

Or imported from an existing contract:

```scheme
(use-trait can-transfer-tokens
'SC3H92H297DX3YDPFHZGH90G8Z4NPH4VE8E83YWAQ.contract-defining-trait.can-transfer-tokens)
```

Looking at trait conformance, callee contracts have two different paths.
lgalabru marked this conversation as resolved.
Show resolved Hide resolved
They can either be "compatible" with a trait by defining methods
matching some of the methods defined in a trait, or explicitely declare
conformance using the `impl-trait` statement:

```scheme
(impl-trait 'SC3H92H297DX3YDPFHZGH90G8Z4NPH4VE8E83YWAQ.contract-defining-trait.can-transfer-tokens)
```

Explicit conformance should be prefered when adequate.
It acts as a safeguard by helping the static analysis system to detect
deviations in method signatures before contract deployment.

The following limitations are imposed on contract calls:

1. No dynamic dispatch. At the time of the smart contract creation,
any contracts being called must be specified. Future designs may
enable this by allowing contract principals to be supplied as
function arguments, however, on initial release, we believe
dynamic invocation to be too dangerous to support.
2. Called smart contracts _must_ exist at the time of creation.
3. No cycles may exist in the call graph of a smart contract. This
1. On static dispatches, callee smart contracts _must_ exist at the time of creation.
2. No cycles may exist in the call graph of a smart contract. This
prevents recursion (and re-entrancy bugs). Such structures can
be detected with static analysis of the call graph, and will be
rejected by the network.

The language described here only allows for eager binding of smart
contract function calls-- this makes static analysis easier, and
eliminates the potential for reentrancy bugs. A key benefit of the
static analyzability of this smart contracting language is that _all_
functions that can possibly be called from a given transaction can be
known _a priori_ so that a user can be warned about all side effects
before signing a transaction.
3. `contract-call?` are for inter-contract calls only. Situations
where the caller is also the callee will result in abortion of
the ongoing transaction.

## Principals and Owner Verification

Expand All @@ -166,7 +203,7 @@ priori which functions a given smart contract will ever call.

Another global variable, `contract-caller`, _does_ change during
inter-contract calls. In particular, `contract-caller` is the contract
principal corresponding to the most recent invocation of `contract-call!`.
principal corresponding to the most recent invocation of `contract-call?`.
In the case of a "top-level" invocation, this variable is equal to `tx-sender`.

Assets in the smart contracting language and blockchain are
Expand Down Expand Up @@ -322,7 +359,7 @@ Example:
12234) -> returns owner principal of name represent by integer 12234
```

Just as with the `(contract-call!)` function, the map name and contract
Just as with the `(contract-call?)` function, the map name and contract
principal arguments must be constants, specified at the time of
publishing.

Expand Down
4 changes: 2 additions & 2 deletions src/clarity.rs
Expand Up @@ -125,9 +125,9 @@ fn advance_cli_chain_tip(path: &String) -> (BlockHeaderHash, BlockHeaderHash) {

friendly_expect(tx.execute("CREATE TABLE IF NOT EXISTS cli_chain_tips(id INTEGER PRIMARY KEY AUTOINCREMENT, block_hash TEXT UNIQUE NOT NULL);", NO_PARAMS),
&format!("FATAL: failed to create 'cli_chain_tips' table"));

let parent_block_hash = get_cli_chain_tip(&tx);

let random_bytes = rand::thread_rng().gen::<[u8; 32]>();
let next_block_hash = friendly_expect_opt(BlockHeaderHash::from_bytes(&random_bytes),
"Failed to generate random block header.");
Expand Down
22 changes: 19 additions & 3 deletions src/vm/analysis/analysis_db.rs
@@ -1,10 +1,12 @@
use std::collections::HashMap;
use std::collections::{HashMap, BTreeMap, BTreeSet};

use vm::types::{TypeSignature, FunctionType, QualifiedContractIdentifier};
use vm::types::{TypeSignature, FunctionType, QualifiedContractIdentifier, TraitIdentifier};
use vm::types::signatures::FunctionSignature;
use vm::database::{ClaritySerializable, ClarityDeserializable,
RollbackWrapper, MarfedKV, ClarityBackingStore};
use vm::analysis::errors::{CheckError, CheckErrors, CheckResult};
use vm::analysis::type_checker::{ContractAnalysis};
use vm::representations::{ClarityName};

pub struct AnalysisDatabase <'a> {
store: RollbackWrapper <'a>
Expand Down Expand Up @@ -67,7 +69,7 @@ impl <'a> AnalysisDatabase <'a> {
self.store.prepare_for_contract_metadata(contract_identifier, Sha512Trunc256Sum([0; 32]));
}

fn load_contract(&mut self, contract_identifier: &QualifiedContractIdentifier) -> Option<ContractAnalysis> {
pub fn load_contract(&mut self, contract_identifier: &QualifiedContractIdentifier) -> Option<ContractAnalysis> {
self.store.get_metadata(contract_identifier, AnalysisDatabase::storage_key())
// treat NoSuchContract error thrown by get_metadata as an Option::None --
// the analysis will propagate that as a CheckError anyways.
Expand All @@ -80,6 +82,7 @@ impl <'a> AnalysisDatabase <'a> {
if self.store.has_metadata_entry(contract_identifier, key) {
return Err(CheckErrors::ContractAlreadyExists(contract_identifier.to_string()).into())
}

self.store.insert_metadata(contract_identifier, key, &contract.serialize());
Ok(())
}
Expand All @@ -98,6 +101,19 @@ impl <'a> AnalysisDatabase <'a> {
.cloned())
}

pub fn get_defined_trait(&mut self, contract_identifier: &QualifiedContractIdentifier, trait_name: &str) -> CheckResult<Option<BTreeMap<ClarityName, FunctionSignature>>> {
let contract = self.load_contract(contract_identifier)
.ok_or(CheckErrors::NoSuchContract(contract_identifier.to_string()))?;
Ok(contract.get_defined_trait(trait_name)
.cloned())
}

pub fn get_implemented_traits(&mut self, contract_identifier: &QualifiedContractIdentifier) -> CheckResult<BTreeSet<TraitIdentifier>> {
let contract = self.load_contract(contract_identifier)
.ok_or(CheckErrors::NoSuchContract(contract_identifier.to_string()))?;
Ok(contract.implemented_traits)
}

pub fn get_map_type(&mut self, contract_identifier: &QualifiedContractIdentifier, map_name: &str) -> CheckResult<(TypeSignature, TypeSignature)> {
let contract = self.load_contract(contract_identifier)
.ok_or(CheckErrors::NoSuchContract(contract_identifier.to_string()))?;
Expand Down
5 changes: 4 additions & 1 deletion src/vm/analysis/contract_interface_builder/mod.rs
Expand Up @@ -15,7 +15,8 @@ pub fn build_contract_interface(contract_analysis: &ContractAnalysis) -> Contrac
map_types,
fungible_tokens,
non_fungible_tokens,
top_level_expression_sorting: _,
defined_traits: _,
implemented_traits: _,
expressions: _,
contract_identifier: _,
type_map: _,
Expand Down Expand Up @@ -88,6 +89,7 @@ pub enum ContractInterfaceAtomType {
type_f: Box<ContractInterfaceAtomType>,
length: u32,
},
trait_reference,
}

#[derive(Debug, Serialize)]
Expand Down Expand Up @@ -127,6 +129,7 @@ impl ContractInterfaceAtomType {
UIntType => ContractInterfaceAtomType::uint128,
BoolType => ContractInterfaceAtomType::bool,
PrincipalType => ContractInterfaceAtomType::principal,
TraitReferenceType(_) => ContractInterfaceAtomType::trait_reference,
BufferType(len) => ContractInterfaceAtomType::buffer { length: len.into() },
TupleType(sig) => Self::from_tuple_type(sig),
ListType(list_data) => {
Expand Down
21 changes: 21 additions & 0 deletions src/vm/analysis/errors.rs
Expand Up @@ -129,6 +129,17 @@ pub enum CheckErrors {
IllegalOrUnknownFunctionApplication(String),
UnknownFunction(String),

// traits
TraitReferenceUnknown(String),
TraitMethodUnknown(String, String),
ExpectedTraitIdentifier,
ImportTraitBadSignature,
TraitReferenceNotAllowed,
BadTraitImplementation(String, String),
DefineTraitBadSignature,
UnexpectedTraitOrFieldReference,
TraitBasedContractCallInReadOnly,

WriteAttemptedInReadOnly,
AtBlockClosureMustBeReadOnly
}
Expand Down Expand Up @@ -312,13 +323,22 @@ impl DiagnosableError for CheckErrors {
CheckErrors::TooManyExpressions => format!("reached limit of expressions"),
CheckErrors::IllegalOrUnknownFunctionApplication(function_name) => format!("use of illegal / unresolved function '{}", function_name),
CheckErrors::UnknownFunction(function_name) => format!("use of unresolved function '{}'", function_name),
CheckErrors::TraitBasedContractCallInReadOnly => format!("use of trait based contract calls are not allowed in read-only context"),
CheckErrors::WriteAttemptedInReadOnly => format!("expecting read-only statements, detected a writing operation"),
CheckErrors::AtBlockClosureMustBeReadOnly => format!("(at-block ...) closures expect read-only statements, but detected a writing operation"),
CheckErrors::BadTokenName => format!("expecting an token name as an argument"),
CheckErrors::DefineFTBadSignature => format!("(define-token ...) expects a token name as an argument"),
CheckErrors::DefineNFTBadSignature => format!("(define-asset ...) expects an asset name and an asset identifier type signature as arguments"),
CheckErrors::NoSuchNFT(asset_name) => format!("tried to use asset function with a undefined asset ('{}')", asset_name),
CheckErrors::NoSuchFT(asset_name) => format!("tried to use token function with a undefined token ('{}')", asset_name),
CheckErrors::TraitReferenceUnknown(trait_name) => format!("use of undeclared trait <{}>", trait_name),
CheckErrors::TraitMethodUnknown(trait_name, func_name) => format!("method '{}' unspecified in trait <{}>", func_name, trait_name),
CheckErrors::ImportTraitBadSignature => format!("(use-trait ...) expects a trait name and a trait identifier"),
CheckErrors::BadTraitImplementation(trait_name, func_name) => format!("invalid signature for method '{}' regarding trait's specification <{}>", func_name, trait_name),
CheckErrors::ExpectedTraitIdentifier => format!("expecting expression of type trait identifier"),
CheckErrors::UnexpectedTraitOrFieldReference => format!("unexpected use of trait reference or field"),
CheckErrors::DefineTraitBadSignature => format!("invalid trait definition"),
CheckErrors::TraitReferenceNotAllowed => format!("trait references can not be stored"),
CheckErrors::TypeAlreadyAnnotatedFailure | CheckErrors::CheckerImplementationFailure => {
format!("internal error - please file an issue on github.com/blockstack/blockstack-core")
},
Expand All @@ -329,6 +349,7 @@ impl DiagnosableError for CheckErrors {
match &self {
CheckErrors::BadSyntaxBinding => Some(format!("binding syntax example: ((supply int) (ttl int))")),
CheckErrors::BadLetSyntax => Some(format!("'let' syntax example: (let ((supply 1000) (ttl 60)) <next-expression>)")),
CheckErrors::TraitReferenceUnknown(_) => Some(format!("traits should be either defined, with define-trait, or imported, with use-trait.")),
CheckErrors::NoSuchBlockInfoProperty(_) => Some(format!("properties available: time, header-hash, burnchain-header-hash, vrf-seed")),
_ => None
}
Expand Down
6 changes: 3 additions & 3 deletions src/vm/analysis/mod.rs
@@ -1,6 +1,6 @@
pub mod types;
pub mod errors;
pub mod definition_sorter;
pub mod trait_checker;
pub mod type_checker;
pub mod read_only_checker;
pub mod analysis_db;
Expand All @@ -13,8 +13,8 @@ use vm::types::{TypeSignature, QualifiedContractIdentifier};
pub use self::errors::{CheckResult, CheckError, CheckErrors};
pub use self::analysis_db::{AnalysisDatabase};

use self::definition_sorter::DefinitionSorter;
use self::read_only_checker::ReadOnlyChecker;
use self::trait_checker::TraitChecker;
use self::type_checker::TypeChecker;

#[cfg(test)]
Expand Down Expand Up @@ -49,9 +49,9 @@ pub fn run_analysis(contract_identifier: &QualifiedContractIdentifier,

analysis_db.execute(|db| {
let mut contract_analysis = ContractAnalysis::new(contract_identifier.clone(), expressions.to_vec());
DefinitionSorter::run_pass(&mut contract_analysis, db)?;
ReadOnlyChecker::run_pass(&mut contract_analysis, db)?;
TypeChecker::run_pass(&mut contract_analysis, db)?;
TraitChecker::run_pass(&mut contract_analysis, db)?;
if save_contract {
db.insert_contract(&contract_identifier, &contract_analysis)?;
}
Expand Down
30 changes: 19 additions & 11 deletions src/vm/analysis/read_only_checker/mod.rs
@@ -1,5 +1,5 @@
use vm::representations::{SymbolicExpressionType, SymbolicExpression, ClarityName};
use vm::representations::SymbolicExpressionType::{AtomValue, Atom, List, LiteralValue};
use vm::representations::SymbolicExpressionType::{AtomValue, Atom, List, LiteralValue, TraitReference, Field};
use vm::types::{TypeSignature, TupleTypeSignature, Value, PrincipalData, parse_name_type_pairs};
use vm::functions::NativeFunctions;
use vm::functions::define::DefineFunctionsParsed;
Expand Down Expand Up @@ -41,7 +41,7 @@ impl <'a, 'b> ReadOnlyChecker <'a, 'b> {

pub fn run(& mut self, contract_analysis: &mut ContractAnalysis) -> CheckResult<()> {

for exp in contract_analysis.expressions_iter() {
for exp in contract_analysis.expressions.iter() {
let mut result = self.check_reads_only_valid(&exp);
if let Err(ref mut error) = result {
if !error.has_expression() {
Expand Down Expand Up @@ -95,6 +95,9 @@ impl <'a, 'b> ReadOnlyChecker <'a, 'b> {
Map { .. } | NonFungibleToken { .. } | UnboundedFungibleToken { .. } => {
// No arguments to (define-map ...) or (define-non-fungible-token) or fungible tokens without max supplies are eval'ed.
},
Trait { .. } | UseTrait { .. } | ImplTrait { .. } => {
// No arguments to (use-trait ...), (define-trait ...). or (impl-trait) are eval'ed.
},
}
} else {
self.check_read_only(expr)?;
Expand All @@ -108,10 +111,7 @@ impl <'a, 'b> ReadOnlyChecker <'a, 'b> {
/// Note that because of (1), this function _cannot_ short-circuit on read-only.
fn check_read_only(&mut self, expr: &SymbolicExpression) -> CheckResult<bool> {
match expr.expr {
AtomValue(_) | LiteralValue(_) => {
Ok(true)
},
Atom(_) => {
AtomValue(_) | LiteralValue(_) | Atom(_) | TraitReference(_, _) | Field(_) => {
Ok(true)
},
List(ref expression) => {
Expand Down Expand Up @@ -259,15 +259,23 @@ impl <'a, 'b> ReadOnlyChecker <'a, 'b> {
},
ContractCall => {
check_arguments_at_least(2, args)?;
let contract_identifier = match args[0].expr {
SymbolicExpressionType::LiteralValue(Value::Principal(PrincipalData::Contract(ref contract_identifier))) => contract_identifier,
_ => return Err(CheckError::new(CheckErrors::ContractCallExpectName))
};

let function_name = args[1].match_atom()
.ok_or(CheckErrors::ContractCallExpectName)?;

let is_function_read_only = self.db.get_read_only_function_type(&contract_identifier, function_name)?.is_some();
let is_function_read_only = match &args[0].expr {
SymbolicExpressionType::LiteralValue(Value::Principal(PrincipalData::Contract(ref contract_identifier))) => {
self.db.get_read_only_function_type(&contract_identifier, function_name)?.is_some()
},
SymbolicExpressionType::Atom(_trait_reference) => {
// Dynamic dispatch from a readonly-function can only be guaranteed at runtime,
// which would defeat granting a static readonly stamp.
// As such dynamic dispatch is currently forbidden.
false
},
_ => return Err(CheckError::new(CheckErrors::ContractCallExpectName))
};

self.check_all_read_only(&args[2..])
.map(|args_read_only| args_read_only && is_function_read_only)
}
Expand Down