Browse Source

Refactor constructor annotations (#1366)

Lucas Steuernagel 2 năm trước cách đây
mục cha
commit
256b76e2e4
40 tập tin đã thay đổi với 869 bổ sung257 xóa
  1. 1 3
      docs/examples/solana/constructor_annotations.sol
  2. 1 2
      docs/examples/solana/payer_annotation.sol
  3. 5 7
      docs/language/contracts.rst
  4. 12 7
      docs/targets/solana.rst
  5. 2 7
      integration/solana/create_contract.sol
  6. 1 1
      integration/solana/create_contract.spec.ts
  7. 27 4
      solang-parser/src/helpers/fmt.rs
  8. 67 31
      solang-parser/src/lexer.rs
  9. 18 1
      solang-parser/src/pt.rs
  10. 19 11
      solang-parser/src/solidity.lalrpop
  11. 68 1
      solang-parser/src/tests.rs
  12. 1 2
      src/abi/tests.rs
  13. 1 0
      src/codegen/dispatch/substrate.rs
  14. 8 0
      src/sema/ast.rs
  15. 13 0
      src/sema/builtin.rs
  16. 13 0
      src/sema/builtin_structs.rs
  17. 6 1
      src/sema/dotgraphviz.rs
  18. 265 79
      src/sema/function_annotation.rs
  19. 52 3
      src/sema/functions.rs
  20. 2 0
      src/sema/namespace.rs
  21. 55 4
      src/sema/statements.rs
  22. 1 0
      src/sema/tests/mod.rs
  23. 4 1
      src/sema/types.rs
  24. 3 0
      src/sema/variables.rs
  25. 1 0
      src/sema/yul/expression.rs
  26. 1 0
      src/sema/yul/functions.rs
  27. 2 0
      src/sema/yul/tests/expression.rs
  28. 1 2
      tests/codegen_testcases/solidity/solana_payer_account.sol
  29. 2 3
      tests/contract_testcases/solana/annotations/account_name_collision.sol
  30. 16 0
      tests/contract_testcases/solana/annotations/at_whitespace.sol
  31. 35 26
      tests/contract_testcases/solana/annotations/bad_accounts.sol
  32. 25 34
      tests/contract_testcases/solana/annotations/constructor_seeds.sol
  33. 15 12
      tests/contract_testcases/solana/annotations/constructor_seeds_bad.sol
  34. 44 0
      tests/contract_testcases/solana/annotations/parameter_annotation.sol
  35. 0 0
      tests/contract_testcases/substrate/annotations/constructor_seeds.sol
  36. 0 0
      tests/contract_testcases/substrate/annotations/selector_override.sol
  37. 0 0
      tests/contract_testcases/substrate/annotations/selector_override_inherited.sol
  38. 0 0
      tests/contract_testcases/substrate/annotations/solana_seeds.sol
  39. 76 0
      tests/contract_testcases/substrate/annotations/unexpected_annotations.sol
  40. 6 15
      tests/solana_tests/create_contract.rs

+ 1 - 3
docs/examples/solana/constructor_annotations.sol

@@ -3,10 +3,8 @@ contract Foo {
 
     @space(500 + 12)
     @seed("Foo")
-    @seed(seed_val)
-    @bump(bump_val)
     @payer(payer)
-    constructor(bytes seed_val, bytes1 bump_val) {
+    constructor(@seed bytes seed_val, @bump bytes1 bump_val) {
         // ...
     }
 }

+ 1 - 2
docs/examples/solana/payer_annotation.sol

@@ -37,10 +37,9 @@ contract Builder {
 
 @program_id("SoLGijpEqEeXLEqa9ruh7a6Lu4wogd6rM8FNoR7e3wY")
 contract BeingBuilt {
-    @seed(my_seed)
     @space(1024)
     @payer(payer_account)
-    constructor(bytes my_seed) {}
+    constructor(@seed bytes my_seed) {}
 
     function say_this(string text) public pure {
         print(text);

+ 5 - 7
docs/language/contracts.rst

@@ -33,12 +33,6 @@ _______________________
 Contracts can be created using the ``new`` keyword. The contract that is being created might have
 constructor arguments, which need to be provided.
 
-.. include:: ../examples/substrate/contract_new.sol
-  :code: solidity
-
-On Solana, the contract being created must have the ``@program_id()`` annotation, and the address
-of the new account must be specified using the ``{address: new_address}`` syntax.
-
 .. include:: ../examples/substrate/contract_new.sol
   :code: solidity
 
@@ -98,7 +92,8 @@ can use. gas is a ``uint64``.
 Instantiating a contract on Solana
 __________________________________
 
-On Solana, contracts are deployed to a program account, which holds only the contract's executable binary.
+On Solana, the contract being created must have the ``@program_id()`` annotation that specifies the program account to
+which the contract code has been deployed. This account holds only the contract's executable binary.
 When calling a constructor, one needs to provide an address that will serve as the contract's data account,
 by using the call argument ``address``:
 
@@ -122,6 +117,9 @@ For the creation of a contract, the data account must the **first** element in s
 .. include:: ../examples/solana/create_contract_with_metas.sol
   :code: solidity
 
+The sequence of the accounts in the ``AccountMeta`` array matters and must follow the
+:ref:`IDL ordering <account_management>`.
+
 
 Base contracts, abstract contracts and interfaces
 -------------------------------------------------

+ 12 - 7
docs/targets/solana.rst

@@ -227,16 +227,20 @@ is passed to the transaction that runs the constructor code.
 
 Alternatively, the data account can be created by the constructor, on chain. When
 this method is used, some parameters must be specified for the account
-using annotations. Those are placed before the constructor. If there is no
-constructor present, then an empty constructor can be added. The constructor
-arguments can be used in the annotations.
+using annotations. Annotations placed above a constructor can only contain literals or
+constant expressions, as is the case for first ``@seed`` and ``@space`` in the following example.
+Annotations can also refer to constructor arguments when placed next to them, as the second ``@seed`` and
+the ``@bump`` examples below. The ``@payer`` annotation is a special annotation that
+:ref:`declares an account <account_management>`.
+
+If the contract has no constructor, annotations can be paired with an empty constructor.
 
 .. include:: ../examples/solana/constructor_annotations.sol
   :code: solidity
 
 Creating an account needs a payer, so at a minimum the ``@payer`` annotation must be
 specified. If it is missing, then the data account must be created client-side.
-The ``@payer`` annotation declares a Solana account that must be passed in the transaction.
+The ``@payer`` annotation :ref:`declares a Solana account <account_management>` that must be passed in the transaction.
 
 The size of the data account can be specified with ``@space``. This is a
 ``uint64`` expression which can either be a constant or use one of the constructor
@@ -253,9 +257,9 @@ If the data account is going to be a
 `program derived address <https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses>`_,
 then the seeds and bump have to be provided. There can be multiple seeds, and an optional
 single bump. If the bump is not provided, then the seeds must not create an
-account that falls on the curve. The ``@seed`` can be a string literal,
-or a hex string with the format ``hex"4142"``, or a constructor argument of type
-``bytes``. The ``@bump`` must a single byte of type ``bytes1``.
+account that falls on the curve. When placed above the constructor, the ``@seed`` can be a string literal,
+or a hex string with the format ``hex"4142"``. If before an argument, the seed annotation must refer to an argument
+of type ``bytes``. The ``@bump`` must a single byte of type ``bytes1``.
 
 .. _value_transfer:
 
@@ -446,6 +450,7 @@ contracts on chain. Examples are available on Solang's integration tests.
 See `system_instruction_example.sol <https://github.com/hyperledger/solang/blob/main/integration/solana/system_instruction_example.sol>`_
 and `system_instruction.spec.ts <https://github.com/hyperledger/solang/blob/main/integration/solana/system_instruction.spec.ts>`_
 
+.. _account_management:
 
 Solana Account Management
 _________________________

+ 2 - 7
integration/solana/create_contract.sol

@@ -64,10 +64,7 @@ contract Child {
 contract Seed1 {
 
     @payer(payer)
-    @seed(seed)
-    @bump(bump)
-    @space(space)
-    constructor(bytes seed, bytes1 bump, uint64 space) {
+    constructor(@seed bytes seed, @bump bytes1 bump, @space uint64 space) {
         print("In Seed1 constructor");
     }
 
@@ -82,9 +79,7 @@ contract Seed2 {
 
     @payer(payer)
     @seed("sunflower")
-    @seed(seed)
-    @space(space + 23)
-    constructor(bytes seed, uint64 space) {
+    constructor(@seed bytes seed, @space uint64 space) {
         my_seed = seed;
 
         print("In Seed2 constructor");

+ 1 - 1
integration/solana/create_contract.spec.ts

@@ -104,7 +104,7 @@ describe('ChildContract', function () {
 
         const info = await provider.connection.getAccountInfo(address);
 
-        expect(info?.data.length).toEqual(9889 + 23);
+        expect(info?.data.length).toEqual(9889);
 
         const idl = JSON.parse(fs.readFileSync('Seed2.json', 'utf8'));
 

+ 27 - 4
solang-parser/src/helpers/fmt.rs

@@ -49,9 +49,13 @@ impl Display for pt::Annotation {
     fn fmt(&self, f: &mut Formatter<'_>) -> Result {
         f.write_char('@')?;
         self.id.fmt(f)?;
-        f.write_char('(')?;
-        self.value.fmt(f)?;
-        f.write_char(')')
+        if let Some(value) = &self.value {
+            f.write_char('(')?;
+            value.fmt(f)?;
+            f.write_char(')')?;
+        }
+
+        Ok(())
     }
 }
 
@@ -199,6 +203,7 @@ impl Display for pt::NamedArgument {
 
 impl Display for pt::Parameter {
     fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        write_opt!(f, &self.annotation, ' ');
         self.ty.fmt(f)?;
         write_opt!(f, ' ', &self.storage);
         write_opt!(f, ' ', &self.name);
@@ -1253,6 +1258,7 @@ fn rm_underscores(s: &str) -> Cow<'_, str> {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::pt::{Annotation, Loc};
 
     macro_rules! struct_tests {
         ($(pt::$t:ident { $( $f:ident: $e:expr ),* $(,)? } => $expected:expr),* $(,)?) => {
@@ -1411,6 +1417,7 @@ mod tests {
                 ty: expr_ty!($i),
                 storage: None,
                 name: None,
+                annotation: None,
             }
         };
 
@@ -1420,6 +1427,7 @@ mod tests {
                 ty: expr_ty!($i),
                 storage: None,
                 name: Some(id(stringify!($n))),
+                annotation: None,
             }
         };
 
@@ -1429,6 +1437,7 @@ mod tests {
                 ty: expr_ty!($i),
                 storage: Some(storage!($s)),
                 name: Some(id(stringify!($n))),
+                annotation: None,
             }
         };
     }
@@ -1496,7 +1505,7 @@ mod tests {
         struct_tests![
             pt::Annotation {
                 id: id("name"),
-                value: expr!(value),
+                value: Some(expr!(value)),
             } => "@name(value)",
 
             pt::Base {
@@ -1572,22 +1581,36 @@ mod tests {
                 ty: expr_ty!(uint256),
                 storage: None,
                 name: None,
+                annotation: None,
             } => "uint256",
             pt::Parameter {
                 ty: expr_ty!(uint256),
                 storage: None,
                 name: Some(id("name")),
+                annotation: None,
             } => "uint256 name",
             pt::Parameter {
                 ty: expr_ty!(uint256),
                 storage: Some(pt::StorageLocation::Calldata(Default::default())),
                 name: Some(id("name")),
+                annotation: None,
             } => "uint256 calldata name",
             pt::Parameter {
                 ty: expr_ty!(uint256),
                 storage: Some(pt::StorageLocation::Calldata(Default::default())),
                 name: None,
+                annotation: None,
             } => "uint256 calldata",
+            pt::Parameter {
+                ty: expr_ty!(bytes),
+                storage: None,
+                name: Some(id("my_seed")),
+                annotation: Some(Annotation {
+                    loc: Loc::Builtin,
+                    id: id("name"),
+                    value: None,
+                }),
+            } => "@name bytes my_seed",
 
             pt::StringLiteral {
                 unicode: false,

+ 67 - 31
solang-parser/src/lexer.rs

@@ -178,7 +178,8 @@ pub enum Token<'input> {
     Case,
     Default,
     YulArrow,
-    At,
+
+    Annotation(&'input str),
 }
 
 impl<'input> fmt::Display for Token<'input> {
@@ -314,7 +315,7 @@ impl<'input> fmt::Display for Token<'input> {
             Token::Case => write!(f, "case"),
             Token::Default => write!(f, "default"),
             Token::YulArrow => write!(f, "->"),
-            Token::At => write!(f, "@"),
+            Token::Annotation(name) => write!(f, "@{name}"),
         }
     }
 }
@@ -745,22 +746,7 @@ impl<'input> Lexer<'input> {
         'toplevel: loop {
             match self.chars.next() {
                 Some((start, ch)) if ch == '_' || ch == '$' || UnicodeXID::is_xid_start(ch) => {
-                    let end;
-
-                    loop {
-                        if let Some((i, ch)) = self.chars.peek() {
-                            if !UnicodeXID::is_xid_continue(*ch) && *ch != '$' {
-                                end = *i;
-                                break;
-                            }
-                            self.chars.next();
-                        } else {
-                            end = self.input.len();
-                            break;
-                        }
-                    }
-
-                    let id = &self.input[start..end];
+                    let (id, end) = self.match_identifier(start);
 
                     if id == "unicode" {
                         match self.chars.peek() {
@@ -974,7 +960,17 @@ impl<'input> Lexer<'input> {
                         Ok(parse_result) => return Some(parse_result),
                     }
                 }
-                Some((i, '@')) => return Some((i, Token::At, i + 1)),
+                Some((start, '@')) => {
+                    let (id, end) = self.match_identifier(start);
+                    if id.len() == 1 {
+                        self.errors.push(LexicalError::UnrecognisedToken(
+                            Loc::File(self.file_no, start, start + 1),
+                            id.to_owned(),
+                        ));
+                    } else {
+                        return Some((start, Token::Annotation(&id[1..]), end));
+                    };
+                }
                 Some((i, ';')) => return Some((i, Token::Semicolon, i + 1)),
                 Some((i, ',')) => return Some((i, Token::Comma, i + 1)),
                 Some((i, '(')) => return Some((i, Token::OpenParenthesis, i + 1)),
@@ -982,19 +978,19 @@ impl<'input> Lexer<'input> {
                 Some((i, '{')) => return Some((i, Token::OpenCurlyBrace, i + 1)),
                 Some((i, '}')) => return Some((i, Token::CloseCurlyBrace, i + 1)),
                 Some((i, '~')) => return Some((i, Token::BitwiseNot, i + 1)),
-                Some((i, '=')) => match self.chars.peek() {
-                    Some((_, '=')) => {
-                        self.chars.next();
-                        return Some((i, Token::Equal, i + 2));
-                    }
-                    Some((_, '>')) => {
-                        self.chars.next();
-                        return Some((i, Token::Arrow, i + 2));
-                    }
-                    _ => {
-                        return Some((i, Token::Assign, i + 1));
+                Some((i, '=')) => {
+                    return match self.chars.peek() {
+                        Some((_, '=')) => {
+                            self.chars.next();
+                            Some((i, Token::Equal, i + 2))
+                        }
+                        Some((_, '>')) => {
+                            self.chars.next();
+                            Some((i, Token::Arrow, i + 2))
+                        }
+                        _ => Some((i, Token::Assign, i + 1)),
                     }
-                },
+                }
                 Some((i, '!')) => {
                     return if let Some((_, '=')) = self.chars.peek() {
                         self.chars.next();
@@ -1218,6 +1214,24 @@ impl<'input> Lexer<'input> {
             }
         }
     }
+
+    fn match_identifier(&mut self, start: usize) -> (&'input str, usize) {
+        let end;
+        loop {
+            if let Some((i, ch)) = self.chars.peek() {
+                if !UnicodeXID::is_xid_continue(*ch) && *ch != '$' {
+                    end = *i;
+                    break;
+                }
+                self.chars.next();
+            } else {
+                end = self.input.len();
+                break;
+            }
+        }
+
+        (&self.input[start..end], end)
+    }
 }
 
 impl<'input> Iterator for Lexer<'input> {
@@ -1893,5 +1907,27 @@ mod tests {
         let tokens = Lexer::new(".9e10", 0, &mut comments, &mut errors).collect::<Vec<_>>();
 
         assert_eq!(tokens, vec!((0, Token::RationalNumber("", "9", "10"), 5)));
+
+        errors.clear();
+        comments.clear();
+        let tokens =
+            Lexer::new("@my_annotation", 0, &mut comments, &mut errors).collect::<Vec<_>>();
+        assert_eq!(tokens, vec![(0, Token::Annotation("my_annotation"), 14)]);
+        assert!(errors.is_empty());
+        assert!(comments.is_empty());
+
+        errors.clear();
+        comments.clear();
+        let tokens =
+            Lexer::new("@ my_annotation", 0, &mut comments, &mut errors).collect::<Vec<_>>();
+        assert_eq!(tokens, vec![(2, Token::Identifier("my_annotation"), 15)]);
+        assert_eq!(
+            errors,
+            vec![LexicalError::UnrecognisedToken(
+                Loc::File(0, 0, 1),
+                "@".to_string()
+            )]
+        );
+        assert!(comments.is_empty());
     }
 }

+ 18 - 1
solang-parser/src/pt.rs

@@ -891,7 +891,7 @@ pub struct Annotation {
     /// The identifier.
     pub id: Identifier,
     /// The value.
-    pub value: Expression,
+    pub value: Option<Expression>,
 }
 
 /// A string literal.
@@ -1242,6 +1242,21 @@ impl Expression {
                 | Negate(..)
         )
     }
+
+    /// Returns if the expression is a literal
+    pub fn is_literal(&self) -> bool {
+        matches!(
+            self,
+            Expression::AddressLiteral(..)
+                | Expression::HexLiteral(..)
+                | Expression::BoolLiteral(..)
+                | Expression::NumberLiteral(..)
+                | Expression::ArrayLiteral(..)
+                | Expression::HexNumberLiteral(..)
+                | Expression::RationalNumberLiteral(..)
+                | Expression::StringLiteral(..)
+        )
+    }
 }
 
 /// A parameter.
@@ -1252,6 +1267,8 @@ impl Expression {
 pub struct Parameter {
     /// The code location.
     pub loc: Loc,
+    /// An optional annotation '@annotation'.
+    pub annotation: Option<Annotation>,
     /// The type.
     pub ty: Expression,
     /// The optional memory location.

+ 19 - 11
solang-parser/src/solidity.lalrpop

@@ -137,6 +137,10 @@ SolIdentifier: Identifier = {
     <l:@L> "revert" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "revert".to_string()},
 }
 
+SolAnnotation: Identifier = {
+    <l:@L> <a:annotation> <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: a.to_string()},
+}
+
 SolIdentifierOrError: Option<Identifier> = {
     SolIdentifier => Some(<>),
     ! => {
@@ -282,9 +286,9 @@ TypeDefinition: Box<TypeDefinition> = {
 }
 
 Annotation: Box<Annotation> = {
-    <l:@L> "@" <id:SolIdentifier> "(" <value:Expression> ")" <r:@R> => {
+    <l:@L> <id:SolAnnotation> "(" <value:Expression> ")" <r:@R> => {
         Box::new(Annotation {
-            loc: Loc::File(file_no, l, r), id, value
+            loc: Loc::File(file_no, l, r), id, value: Some(value)
         })
     }
 }
@@ -520,14 +524,12 @@ HexLiteral: HexLiteral = {
     }
 }
 
-// A parameter list is used for function arguments, returns, and destructuring statements.
-// In destructuring statements, parameters can be optional. So, we make parameters optional
-// and as an added bonus we can generate error messages about missing parameters/returns
-// to functions
+// A parameter list is used for function arguments and return.
+// Destructure statements utilize NamedParameter, not Parameter
 Parameter: Parameter = {
-    <l:@L> <ty:Precedence0> <storage:StorageLocation?> <name:SolIdentifier?> <r:@R> => {
+    <l:@L> <annotation:ParameterAnnotation?> <ty:Precedence0> <storage:StorageLocation?> <name:SolIdentifier?> <r:@R> => {
         let loc = Loc::File(file_no, l, r);
-        Parameter{loc, ty, storage, name}
+        Parameter{loc, annotation, ty, storage, name}
     }
 }
 
@@ -545,14 +547,20 @@ ParameterList: Vec<(Loc, Option<Parameter>)> = {
     }
 }
 
+ParameterAnnotation: Annotation = {
+    <l:@L> <id:SolAnnotation> <r:@R> => Annotation {
+        loc: Loc::File(file_no, l, r), id, value: None,
+    }
+}
+
 NamedParameter: Parameter = {
     <l:@L> <ty:Precedence0> <storage:StorageLocation?> <name:SolIdentifier> <r:@R> => {
         let loc = Loc::File(file_no, l, r);
-        Parameter{ loc, ty, storage, name: Some(name) }
+        Parameter{ loc, annotation: None, ty, storage, name: Some(name) }
     },
     <l:@L> <ty:Expression> <r:@R> => {
         let loc = Loc::File(file_no, l, r);
-        Parameter{ loc, ty, storage: None, name: None }
+        Parameter{ loc, annotation: None, ty, storage: None, name: None }
     }
 }
 
@@ -1117,6 +1125,7 @@ extern {
 
     enum Token<'input> {
         identifier => Token::Identifier(<&'input str>),
+        annotation => Token::Annotation(<&'input str>),
         string => Token::StringLiteral(<bool>, <&'input str>),
         hexstring => Token::HexLiteral(<&'input str>),
         address => Token::AddressLiteral(<&'input str>),
@@ -1171,7 +1180,6 @@ extern {
         "]" => Token::CloseBracket,
         "." => Token::Member,
         "," => Token::Comma,
-        "@" => Token::At,
         Uint => Token::Uint(<u16>),
         Int => Token::Int(<u16>),
         Bytes => Token::Bytes(<u8>),

+ 68 - 1
solang-parser/src/tests.rs

@@ -55,7 +55,7 @@ contract 9c {
                 Diagnostic { loc: File(0, 482, 483), level: Error, ty: ParserError, message: "unrecognised token '3', expected \"!=\", \"%\", \"%=\", \"&\", \"&&\", \"&=\", \"(\", \"*\", \"**\", \"*=\", \"+\", \"++\", \"+=\", \"-\", \"--\", \"-=\", \".\", \"/\", \"/=\", \";\", \"<\", \"<<\", \"<<=\", \"<=\", \"=\", \"==\", \">\", \">=\", \">>\", \">>=\", \"?\", \"[\", \"^\", \"^=\", \"calldata\", \"case\", \"default\", \"leave\", \"memory\", \"revert\", \"storage\", \"switch\", \"{\", \"|\", \"|=\", \"||\", identifier".to_string(), notes: vec![] },
                 Diagnostic { loc: File(0, 518, 522), level: Error, ty: ParserError, message: "unrecognised token 'uint256', expected \"!=\", \"%\", \"%=\", \"&\", \"&&\", \"&=\", \"*\", \"**\", \"*=\", \"+\", \"++\", \"+=\", \"-\", \"--\", \"-=\", \".\", \"/\", \"/=\", \";\", \"<\", \"<<\", \"<<=\", \"<=\", \"=\", \"==\", \">\", \">=\", \">>\", \">>=\", \"?\", \"[\", \"^\", \"^=\", \"case\", \"default\", \"leave\", \"switch\", \"|\", \"|=\", \"||\", identifier".to_string(), notes: vec![] },
                 Diagnostic { loc: File(0, 555, 556), level: Error, ty: ParserError, message: "unrecognised token '}', expected \"!\", \"(\", \"+\", \"++\", \"-\", \"--\", \"[\", \"address\", \"assembly\", \"bool\", \"break\", \"byte\", \"bytes\", \"case\", \"continue\", \"default\", \"delete\", \"do\", \"emit\", \"false\", \"for\", \"function\", \"if\", \"leave\", \"mapping\", \"new\", \"payable\", \"return\", \"revert\", \"string\", \"switch\", \"true\", \"try\", \"type\", \"unchecked\", \"while\", \"{\", \"~\", Bytes, Int, Uint, address, hexnumber, hexstring, identifier, number, rational, string".to_string(), notes: vec![] },
-                Diagnostic { loc: File(0, 557, 558), level: Error, ty: ParserError, message: "unrecognised token '}', expected \"(\", \";\", \"@\", \"[\", \"abstract\", \"address\", \"bool\", \"byte\", \"bytes\", \"case\", \"contract\", \"default\", \"enum\", \"event\", \"false\", \"function\", \"import\", \"interface\", \"leave\", \"library\", \"mapping\", \"payable\", \"pragma\", \"string\", \"struct\", \"switch\", \"true\", \"type\", \"using\", Bytes, Int, Uint, address, hexnumber, hexstring, identifier, number, rational, string".to_string(), notes: vec![] }
+                Diagnostic { loc: File(0, 557, 558), level: Error, ty: ParserError, message: "unrecognised token '}', expected \"(\", \";\", \"[\", \"abstract\", \"address\", \"bool\", \"byte\", \"bytes\", \"case\", \"contract\", \"default\", \"enum\", \"event\", \"false\", \"function\", \"import\", \"interface\", \"leave\", \"library\", \"mapping\", \"payable\", \"pragma\", \"string\", \"struct\", \"switch\", \"true\", \"type\", \"using\", Bytes, Int, Uint, address, annotation, hexnumber, hexstring, identifier, number, rational, string".to_string(), notes: vec![] }
             ]
         )
     }
@@ -232,6 +232,7 @@ fn parse_test() {
                                     loc: Loc::File(0, 785, 788),
                                     name: "sum".to_string(),
                                 }),
+                                annotation: None,
                             }),
                         )],
                         Box::new(Statement::Block {
@@ -273,6 +274,7 @@ fn parse_test() {
                                     loc: Loc::File(0, 876, 877),
                                     name: "b".to_string(),
                                 }),
+                                annotation: None,
                             }),
                             Statement::Block {
                                 loc: Loc::File(0, 879, 950),
@@ -302,6 +304,7 @@ fn parse_test() {
                                     loc: Loc::File(0, 977, 982),
                                     name: "error".to_string(),
                                 }),
+                                annotation: None,
                             },
                             Statement::Block {
                                 loc: Loc::File(0, 984, 1046),
@@ -330,6 +333,7 @@ fn parse_test() {
                                     loc: Loc::File(0, 1064, 1065),
                                     name: "x".to_string(),
                                 }),
+                                annotation: None,
                             },
                             Statement::Block {
                                 loc: Loc::File(0, 1067, 1129),
@@ -1267,3 +1271,66 @@ fn test_libsolidity() {
 
     assert!(errors.is_empty(), "{}", errors.join("\n"));
 }
+
+#[test]
+fn parameter_annotation() {
+    let src = r#"
+contract MyTest {
+    constructor(@seed bytes mySeed) {}
+}
+    "#;
+
+    let (actual_parse_tree, _) = crate::parse(src, 0).unwrap();
+
+    let expected_tree = SourceUnit(vec![SourceUnitPart::ContractDefinition(
+        ContractDefinition {
+            loc: File(0, 1, 59),
+            ty: ContractTy::Contract(File(0, 1, 9)),
+            name: Some(Identifier {
+                loc: File(0, 10, 16),
+                name: "MyTest".to_string(),
+            }),
+            base: vec![],
+            parts: vec![ContractPart::FunctionDefinition(
+                FunctionDefinition {
+                    loc: File(0, 23, 55),
+                    ty: FunctionTy::Constructor,
+                    name: None,
+                    name_loc: File(0, 34, 34),
+                    params: (vec![(
+                        File(0, 35, 53),
+                        Some(Parameter {
+                            loc: File(0, 35, 53),
+                            ty: Expression::Type(File(0, 41, 46), Type::DynamicBytes),
+                            storage: None,
+                            name: Some(Identifier {
+                                loc: File(0, 47, 53),
+                                name: "mySeed".to_string(),
+                            }),
+                            annotation: Some(Annotation {
+                                loc: File(0, 35, 40),
+                                id: Identifier {
+                                    loc: File(0, 35, 40),
+                                    name: "seed".to_string(),
+                                },
+                                value: None,
+                            }),
+                        }),
+                    )]),
+                    attributes: vec![],
+                    return_not_returns: None,
+                    returns: vec![],
+                    body: Some(Statement::Block {
+                        loc: File(0, 55, 57),
+                        unchecked: false,
+                        statements: vec![],
+                    }),
+                }
+                .into(),
+            )],
+        }
+        .into(),
+    )]);
+
+    assert_eq!(expected_tree, actual_parse_tree);
+}

+ 1 - 2
src/abi/tests.rs

@@ -2276,10 +2276,9 @@ contract Builder {
 
 @program_id("SoLGijpEqEeXLEqa9ruh7a6Lu4wogd6rM8FNoR7e3wY")
 contract BeingBuilt {
-    @seed(my_seed)
     @space(1024)
     @payer(other_account)
-    constructor(bytes my_seed) {}
+    constructor(@seed bytes my_seed) {}
 
     function say_this(string text) public pure {
         print(text);

+ 1 - 0
src/codegen/dispatch/substrate.rs

@@ -56,6 +56,7 @@ fn new_cfg(ns: &Namespace) -> ControlFlowGraph {
         readonly: true,
         infinite_size: false,
         recursive: false,
+        annotation: None,
     };
     let mut input_len = input_ptr.clone();
     input_len.ty = Uint(32);

+ 8 - 0
src/sema/ast.rs

@@ -253,6 +253,14 @@ pub struct Parameter {
     /// Is this struct field recursive. Recursive does not mean infinite size in all cases:
     /// `struct S { S[] s }` is recursive but not of infinite size.
     pub recursive: bool,
+
+    pub annotation: Option<ParameterAnnotation>,
+}
+
+#[derive(Debug, Eq, Clone, PartialEq)]
+pub struct ParameterAnnotation {
+    pub loc: pt::Loc,
+    pub id: pt::Identifier,
 }
 
 impl Parameter {

+ 13 - 0
src/sema/builtin.rs

@@ -1511,6 +1511,7 @@ impl Namespace {
                     indexed: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -1521,6 +1522,7 @@ impl Namespace {
                     indexed: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
             ],
             vec![Parameter {
@@ -1532,6 +1534,7 @@ impl Namespace {
                 indexed: false,
                 infinite_size: false,
                 recursive: false,
+                annotation: None,
             }],
             self,
         );
@@ -1574,6 +1577,7 @@ impl Namespace {
                     indexed: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -1584,6 +1588,7 @@ impl Namespace {
                     indexed: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
             ],
             vec![
@@ -1596,6 +1601,7 @@ impl Namespace {
                     indexed: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -1606,6 +1612,7 @@ impl Namespace {
                     indexed: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
             ],
             self,
@@ -1680,6 +1687,7 @@ impl Namespace {
                     indexed: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc,
@@ -1690,6 +1698,7 @@ impl Namespace {
                     indexed: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
             ],
             vec![
@@ -1702,6 +1711,7 @@ impl Namespace {
                     indexed: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc,
@@ -1712,6 +1722,7 @@ impl Namespace {
                     indexed: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
             ],
             self,
@@ -1742,6 +1753,7 @@ impl Namespace {
                 indexed: false,
                 infinite_size: false,
                 recursive: false,
+                annotation: None,
             }],
             vec![Parameter {
                 loc,
@@ -1752,6 +1764,7 @@ impl Namespace {
                 indexed: false,
                 infinite_size: false,
                 recursive: false,
+                annotation: None,
             }],
             self,
         );

+ 13 - 0
src/sema/builtin_structs.rs

@@ -24,6 +24,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: true,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -37,6 +38,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -50,6 +52,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: true,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -63,6 +66,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: true,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -76,6 +80,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: true,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -89,6 +94,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: true,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -102,6 +108,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: true,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -115,6 +122,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: true,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
             ],
             offsets: Vec::new(),
@@ -138,6 +146,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -151,6 +160,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -164,6 +174,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
             ],
             offsets: Vec::new(),
@@ -184,6 +195,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 Parameter {
                     loc: pt::Loc::Builtin,
@@ -194,6 +206,7 @@ static BUILTIN_STRUCTS: Lazy<[StructDecl; 3]> = Lazy::new(|| {
                     readonly: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
             ],
             offsets: Vec::new(),

+ 6 - 1
src/sema/dotgraphviz.rs

@@ -176,7 +176,12 @@ impl Dot {
 
             for param in &*func.params {
                 labels.push(format!(
-                    "{} {}",
+                    "{}{} {}",
+                    if let Some(annotation) = &param.annotation {
+                        format!("@{} ", annotation.id.name)
+                    } else {
+                        String::new()
+                    },
                     param.ty.to_string(ns),
                     param.name_as_str()
                 ));

+ 265 - 79
src/sema/function_annotation.rs

@@ -6,19 +6,35 @@ use super::{
     eval::overflow_check,
     expression::literals::{hex_number_literal, unit_literal},
     expression::{ExprContext, ResolveTo},
-    unused_variable::used_variable,
     Symtable,
 };
 use crate::sema::ast::SolanaAccount;
+use crate::sema::eval::eval_const_number;
 use crate::sema::expression::literals::number_literal;
 use crate::sema::expression::resolve_expression::expression;
 use crate::sema::solana_accounts::BuiltinAccounts;
 use crate::Target;
 use indexmap::map::Entry;
 use num_traits::ToPrimitive;
-use solang_parser::pt::{self, CodeLocation};
+use solang_parser::pt::{self, Annotation, CodeLocation};
 use std::str::FromStr;
 
+/// Annotations are processed in two different places during sema. When we are resolving the
+/// function header, we collect the parameter annotations in 'UnresolvedAnnotation' data structure.
+/// Afterwards, during the function body resolution, we resolve the annotations.
+pub(super) struct UnresolvedAnnotation {
+    /// The parameter to which this annotation is attached.
+    pub(super) parameter_no: usize,
+    /// Variable number of this parameter in the symbol table.
+    pub(super) var_no: usize,
+}
+
+/// This function simplifies the addition of a common error when we encounter a mispalced
+/// annotation.
+pub(super) fn unexpected_parameter_annotation(loc: pt::Loc) -> Diagnostic {
+    Diagnostic::error(loc, "unexpected parameter annotation".to_string())
+}
+
 /// Resolve the prototype annotation for functions (just the selector). These
 /// annotations can be resolved for functions without a body. This means they
 /// do not need to access the symbol table, like `@seed(foo)` annotations do.
@@ -80,16 +96,17 @@ fn function_selector(
     }
 
     if let Some((prev, _)) = &func.selector {
-        diagnostics.push(Diagnostic::error_with_note(
+        duplicate_annotation(
+            diagnostics,
+            "selector",
             annotation.loc,
-            format!("duplicate @selector annotation for {}", func.ty),
             *prev,
-            "previous @selector".into(),
-        ));
+            func.ty.as_str(),
+        );
         return;
     }
 
-    match &annotation.value {
+    match &annotation.value.as_ref().unwrap() {
         pt::Expression::ArrayLiteral(_, values) => {
             let mut selector = Vec::new();
 
@@ -140,7 +157,7 @@ fn function_selector(
         }
         _ => {
             diagnostics.push(Diagnostic::error(
-                annotation.value.loc(),
+                annotation.value.as_ref().unwrap().loc(),
                 "expression must be an array literal".into(),
             ));
         }
@@ -151,9 +168,10 @@ fn function_selector(
 /// there should be no seed or bump annotations permitted on other targets.
 ///
 /// These annotations need a symbol table.
-pub fn function_body_annotations(
+pub(super) fn function_body_annotations(
     function_no: usize,
-    annotations: &[&pt::Annotation],
+    body_annotations: &[&pt::Annotation],
+    parameter_annotations: &[UnresolvedAnnotation],
     symtable: &mut Symtable,
     context: &ExprContext,
     ns: &mut Namespace,
@@ -175,7 +193,7 @@ pub fn function_body_annotations(
     let is_solana_constructor =
         ns.target == Target::Solana && ns.functions[function_no].ty == pt::FunctionTy::Constructor;
 
-    for note in annotations {
+    for note in body_annotations {
         match note.id.name.as_str() {
             "selector" => {
                 // selectors already done in function_prototype_annotations
@@ -183,91 +201,54 @@ pub fn function_body_annotations(
             }
             "seed" if is_solana_constructor => {
                 let ty = Type::Slice(Box::new(Type::Bytes(1)));
-                let loc = note.loc;
 
-                if let Ok(expr) = expression(
-                    &note.value,
+                let mut fake_loc = None;
+
+                body_annotation(
+                    note.id.name.as_str(),
+                    &ty,
+                    &mut fake_loc,
+                    note,
+                    &mut diagnostics,
+                    &mut resolved_annotations,
                     context,
                     ns,
                     symtable,
-                    &mut diagnostics,
-                    ResolveTo::Type(&ty),
-                ) {
-                    if let Ok(expr) = expr.cast(&expr.loc(), &ty, true, ns, &mut diagnostics) {
-                        if let Some(prev) = &bump {
-                            diagnostics.push(Diagnostic::error_with_note(
-                                *prev,
-                                "@bump should be after the last @seed".into(),
-                                loc,
-                                "location of @seed annotation".into(),
-                            ));
-                        } else {
-                            used_variable(ns, &expr, symtable);
-                            resolved_annotations.push(ConstructorAnnotation::Seed(expr));
-                        }
-                    }
-                }
+                );
             }
             "bump" if is_solana_constructor => {
                 let ty = Type::Bytes(1);
-                let loc = note.loc;
 
-                if let Ok(expr) = expression(
-                    &note.value,
+                body_annotation(
+                    note.id.name.as_str(),
+                    &ty,
+                    &mut bump,
+                    note,
+                    &mut diagnostics,
+                    &mut resolved_annotations,
                     context,
                     ns,
                     symtable,
-                    &mut diagnostics,
-                    ResolveTo::Type(&ty),
-                ) {
-                    if let Ok(expr) = expr.cast(&expr.loc(), &ty, true, ns, &mut diagnostics) {
-                        if let Some(prev) = &bump {
-                            diagnostics.push(Diagnostic::error_with_note(
-                                expr.loc(),
-                                "duplicate @bump annotation for constructor".into(),
-                                *prev,
-                                "previous @bump".into(),
-                            ));
-                        } else {
-                            bump = Some(loc);
-                            used_variable(ns, &expr, symtable);
-                            resolved_annotations.push(ConstructorAnnotation::Bump(expr));
-                        }
-                    }
-                }
+                );
             }
             "space" if is_solana_constructor => {
                 let ty = Type::Uint(64);
-                let loc = note.loc;
 
-                if let Ok(expr) = expression(
-                    &note.value,
+                body_annotation(
+                    note.id.name.as_str(),
+                    &ty,
+                    &mut space,
+                    note,
+                    &mut diagnostics,
+                    &mut resolved_annotations,
                     context,
                     ns,
                     symtable,
-                    &mut diagnostics,
-                    ResolveTo::Type(&ty),
-                ) {
-                    if let Ok(expr) = expr.cast(&expr.loc(), &ty, true, ns, &mut diagnostics) {
-                        if let Some(prev) = &space {
-                            diagnostics.push(Diagnostic::error_with_note(
-                                loc,
-                                "duplicate @space annotation for constructor".into(),
-                                *prev,
-                                "previous @space".into(),
-                            ));
-                        } else {
-                            space = Some(loc);
-                            used_variable(ns, &expr, symtable);
-                            resolved_annotations.push(ConstructorAnnotation::Space(expr));
-                        }
-                    }
-                }
+                );
             }
             "payer" if is_solana_constructor => {
                 let loc = note.loc;
-
-                if let pt::Expression::Variable(id) = &note.value {
+                if let pt::Expression::Variable(id) = note.value.as_ref().unwrap() {
                     if BuiltinAccounts::from_str(&id.name).is_ok() {
                         diagnostics.push(Diagnostic::error(
                             id.loc,
@@ -291,12 +272,13 @@ pub fn function_body_annotations(
                         }
                         Entry::Vacant(vacancy) => {
                             if let Some(prev) = &payer {
-                                diagnostics.push(Diagnostic::error_with_note(
+                                duplicate_annotation(
+                                    &mut diagnostics,
+                                    "payer",
                                     loc,
-                                    "duplicate @payer annotation for constructor".into(),
                                     *prev,
-                                    "previous @payer".into(),
-                                ));
+                                    ns.functions[function_no].ty.as_str(),
+                                );
                             } else {
                                 payer = Some(loc);
                                 vacancy.insert(SolanaAccount {
@@ -327,6 +309,83 @@ pub fn function_body_annotations(
         };
     }
 
+    for unresolved in parameter_annotations {
+        match ns.functions[function_no].params[unresolved.parameter_no]
+            .annotation
+            .as_ref()
+            .unwrap()
+            .id
+            .name
+            .as_str()
+        {
+            "seed" => {
+                let ty = Type::Slice(Box::new(Type::Bytes(1)));
+                let mut fake_loc = None;
+                parameter_annotation(
+                    function_no,
+                    unresolved,
+                    &ty,
+                    &mut fake_loc,
+                    &mut diagnostics,
+                    &mut resolved_annotations,
+                    ns,
+                    symtable,
+                );
+            }
+            "bump" => {
+                let ty = Type::Bytes(1);
+                parameter_annotation(
+                    function_no,
+                    unresolved,
+                    &ty,
+                    &mut bump,
+                    &mut diagnostics,
+                    &mut resolved_annotations,
+                    ns,
+                    symtable,
+                );
+            }
+            "space" => {
+                let ty = Type::Uint(64);
+                parameter_annotation(
+                    function_no,
+                    unresolved,
+                    &ty,
+                    &mut space,
+                    &mut diagnostics,
+                    &mut resolved_annotations,
+                    ns,
+                    symtable,
+                );
+            }
+
+            "payer" => {
+                diagnostics.push(Diagnostic::error(
+                    ns.functions[function_no].params[unresolved.parameter_no]
+                        .annotation
+                        .as_ref()
+                        .unwrap()
+                        .loc,
+                    "@payer annotation not allowed next to a parameter".to_string(),
+                ));
+            }
+
+            _ => {
+                let annotation = ns.functions[function_no].params[unresolved.parameter_no]
+                    .annotation
+                    .as_ref()
+                    .unwrap();
+                diagnostics.push(Diagnostic::error(
+                    annotation.loc,
+                    format!(
+                        "unknown annotation {} for {}",
+                        annotation.id.name, ns.functions[function_no].ty
+                    ),
+                ))
+            }
+        }
+    }
+
     if !resolved_annotations.is_empty() && diagnostics.is_empty() && payer.is_none() {
         diagnostics.push(Diagnostic::error(
             ns.functions[function_no].loc,
@@ -338,3 +397,130 @@ pub fn function_body_annotations(
 
     ns.functions[function_no].annotations = resolved_annotations;
 }
+
+/// Resolve the body annotations
+fn body_annotation(
+    name: &str,
+    ty: &Type,
+    previous: &mut Option<pt::Loc>,
+    annotation: &Annotation,
+    diagnostics: &mut Diagnostics,
+    resolved_annotations: &mut Vec<ConstructorAnnotation>,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+) {
+    let annotation_value = annotation.value.as_ref().unwrap();
+    let mut dry_run = Diagnostics::default();
+    if let Ok(expr) = expression(
+        annotation_value,
+        context,
+        ns,
+        symtable,
+        &mut dry_run,
+        ResolveTo::Type(ty),
+    ) {
+        // We only accept literals or constant expressions.
+        if !annotation_value.is_literal() && eval_const_number(&expr, ns, &mut dry_run).is_err() {
+            diagnostics.push(Diagnostic::error(
+                annotation.value.as_ref().unwrap().loc(),
+                format!(
+                    "'@{}' annotation on top of a constructor only accepts literals",
+                    name
+                ),
+            ));
+            return;
+        }
+    }
+
+    if let Ok(expr) = expression(
+        annotation_value,
+        context,
+        ns,
+        symtable,
+        diagnostics,
+        ResolveTo::Type(ty),
+    ) {
+        if let Ok(expr) = expr.cast(&expr.loc(), ty, true, ns, diagnostics) {
+            if let Some(prev) = previous {
+                duplicate_annotation(diagnostics, name, expr.loc(), *prev, "constructor");
+            } else {
+                *previous = Some(annotation.loc);
+                resolved_annotations.push(ConstructorAnnotation::initialize_annotation(name, expr));
+            }
+        }
+    }
+}
+
+/// Resolve parameter annotations
+fn parameter_annotation(
+    function_no: usize,
+    unresolved_annotation: &UnresolvedAnnotation,
+    ty: &Type,
+    previous: &mut Option<pt::Loc>,
+    diagnostics: &mut Diagnostics,
+    resolved_annotations: &mut Vec<ConstructorAnnotation>,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+) {
+    let parameter = &ns.functions[function_no].params[unresolved_annotation.parameter_no];
+    let annotation = parameter.annotation.as_ref().unwrap();
+    if let Some(prev) = previous {
+        duplicate_annotation(
+            diagnostics,
+            annotation.id.name.as_str(),
+            annotation.loc,
+            *prev,
+            "constructor",
+        );
+        return;
+    }
+
+    let expr = Expression::Variable {
+        loc: annotation.loc,
+        ty: parameter.ty.clone(),
+        var_no: unresolved_annotation.var_no,
+    };
+
+    // Mark variable as used, without using 'ns' (I cannot borrow it as mutable here)
+    symtable
+        .vars
+        .get_mut(&unresolved_annotation.var_no)
+        .unwrap()
+        .read = true;
+
+    if let Ok(casted) = expr.cast(&annotation.loc, ty, true, ns, diagnostics) {
+        *previous = Some(annotation.loc);
+        resolved_annotations.push(ConstructorAnnotation::initialize_annotation(
+            annotation.id.name.as_str(),
+            casted,
+        ));
+    }
+}
+
+impl ConstructorAnnotation {
+    fn initialize_annotation(name: &str, value: Expression) -> ConstructorAnnotation {
+        match name {
+            "seed" => ConstructorAnnotation::Seed(value),
+            "space" => ConstructorAnnotation::Space(value),
+            "bump" => ConstructorAnnotation::Bump(value),
+            _ => unreachable!("function should not be called with {}", name),
+        }
+    }
+}
+
+/// This function centralizes where we generate the duplicate annotation error.
+fn duplicate_annotation(
+    diagnostics: &mut Diagnostics,
+    name: &str,
+    new_loc: pt::Loc,
+    old_loc: pt::Loc,
+    func_ty: &str,
+) {
+    diagnostics.push(Diagnostic::error_with_note(
+        new_loc,
+        format!("duplicate @{} annotation for {}", name, func_ty),
+        old_loc,
+        format!("previous @{}", name),
+    ));
+}

+ 52 - 3
src/sema/functions.rs

@@ -8,7 +8,10 @@ use super::{
     tags::resolve_tags,
     ContractDefinition,
 };
+use crate::sema::ast::ParameterAnnotation;
+use crate::sema::function_annotation::unexpected_parameter_annotation;
 use crate::Target;
+use solang_parser::pt::FunctionTy;
 use solang_parser::{
     doccomment::DocComment,
     pt,
@@ -305,6 +308,7 @@ pub fn contract_function(
 
     let (params, params_success) = resolve_params(
         &func.params,
+        &func.ty,
         storage_allowed,
         file_no,
         Some(contract_no),
@@ -740,8 +744,15 @@ pub fn function(
 
     let mut diagnostics = Diagnostics::default();
 
-    let (params, params_success) =
-        resolve_params(&func.params, true, file_no, None, ns, &mut diagnostics);
+    let (params, params_success) = resolve_params(
+        &func.params,
+        &func.ty,
+        true,
+        file_no,
+        None,
+        ns,
+        &mut diagnostics,
+    );
 
     let (returns, returns_success) =
         resolve_returns(&func.returns, true, file_no, None, ns, &mut diagnostics);
@@ -828,6 +839,7 @@ pub fn function(
 /// Resolve the parameters
 pub fn resolve_params(
     parameters: &[(pt::Loc, Option<pt::Parameter>)],
+    func_ty: &pt::FunctionTy,
     is_internal: bool,
     file_no: usize,
     contract_no: Option<usize>,
@@ -839,7 +851,27 @@ pub fn resolve_params(
 
     for (loc, p) in parameters {
         let p = match p {
-            Some(p) => p,
+            Some(p @ pt::Parameter { ref annotation, .. }) => {
+                if annotation.is_some()
+                    && *func_ty != FunctionTy::Constructor
+                    && ns.target == Target::Solana
+                {
+                    diagnostics.push(Diagnostic::error(
+                        annotation.as_ref().unwrap().loc,
+                        "parameter annotations are only allowed in constructors".to_string(),
+                    ));
+                    success = false;
+                    continue;
+                } else if annotation.is_some() && ns.target != Target::Solana {
+                    diagnostics.push(unexpected_parameter_annotation(
+                        annotation.as_ref().unwrap().loc,
+                    ));
+                    success = false;
+                    continue;
+                }
+
+                p
+            }
             None => {
                 diagnostics.push(Diagnostic::error(*loc, "missing parameter type".to_owned()));
                 success = false;
@@ -914,6 +946,11 @@ pub fn resolve_params(
                     ty
                 };
 
+                let annotation = p.annotation.as_ref().map(|e| ParameterAnnotation {
+                    loc: e.loc,
+                    id: e.id.clone(),
+                });
+
                 params.push(Parameter {
                     loc: *loc,
                     id: p.name.clone(),
@@ -923,6 +960,7 @@ pub fn resolve_params(
                     readonly: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation,
                 });
             }
             Err(()) => success = false,
@@ -946,6 +984,14 @@ pub fn resolve_returns(
 
     for (loc, r) in returns {
         let r = match r {
+            Some(pt::Parameter {
+                annotation: Some(annotation),
+                ..
+            }) => {
+                diagnostics.push(unexpected_parameter_annotation(annotation.loc));
+                success = false;
+                continue;
+            }
             Some(r) => r,
             None => {
                 diagnostics.push(Diagnostic::error(*loc, "missing return type".to_owned()));
@@ -1036,6 +1082,7 @@ pub fn resolve_returns(
                     readonly: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 });
             }
             Err(()) => success = false,
@@ -1076,6 +1123,7 @@ fn signatures() {
                 readonly: false,
                 infinite_size: false,
                 recursive: false,
+                annotation: None,
             },
             Parameter {
                 loc: pt::Loc::Implicit,
@@ -1086,6 +1134,7 @@ fn signatures() {
                 readonly: false,
                 infinite_size: false,
                 recursive: false,
+                annotation: None,
             },
         ],
         Vec::new(),

+ 2 - 0
src/sema/namespace.rs

@@ -18,6 +18,7 @@ use crate::Target;
 use num_bigint::BigInt;
 use num_traits::Signed;
 use num_traits::Zero;
+use solang_parser::pt::FunctionTy;
 use solang_parser::{
     pt,
     pt::{CodeLocation, OptionalCodeLocation},
@@ -1055,6 +1056,7 @@ impl Namespace {
 
                     let (params, params_success) = resolve_params(
                         params,
+                        &FunctionTy::Function,
                         is_external,
                         file_no,
                         contract_no,

+ 55 - 4
src/sema/statements.rs

@@ -17,6 +17,7 @@ use crate::sema::expression::function_call::{
 };
 use crate::sema::expression::resolve_expression::expression;
 use crate::sema::function_annotation::function_body_annotations;
+use crate::sema::function_annotation::{unexpected_parameter_annotation, UnresolvedAnnotation};
 use crate::sema::symtable::{VariableInitializer, VariableUsage};
 use crate::sema::unused_variable::{assigned_variable, check_function_call, used_variable};
 use crate::sema::yul::resolve_inline_assembly;
@@ -50,6 +51,7 @@ pub fn resolve_function_body(
         yul_function: false,
     };
 
+    let mut unresolved_annotation: Vec<UnresolvedAnnotation> = Vec::new();
     // first add function parameters
     for (i, p) in def.params.iter().enumerate() {
         let p = p.1.as_ref().unwrap();
@@ -63,7 +65,12 @@ pub fn resolve_function_body(
                 p.storage.clone(),
             ) {
                 ns.check_shadowing(file_no, contract_no, name);
-
+                if p.annotation.is_some() {
+                    unresolved_annotation.push(UnresolvedAnnotation {
+                        parameter_no: i,
+                        var_no: pos,
+                    });
+                }
                 symtable.arguments.push(Some(pos));
             }
         } else {
@@ -71,7 +78,14 @@ pub fn resolve_function_body(
         }
     }
 
-    function_body_annotations(function_no, annotations, &mut symtable, &context, ns);
+    function_body_annotations(
+        function_no,
+        annotations,
+        &unresolved_annotation,
+        &mut symtable,
+        &context,
+        ns,
+    );
 
     // now that the function arguments have been resolved, we can resolve the bases for
     // constructors.
@@ -391,6 +405,7 @@ fn statement(
                         readonly: false,
                         infinite_size: false,
                         recursive: false,
+                        annotation: None,
                     },
                     initializer,
                 ));
@@ -1478,12 +1493,17 @@ fn destructure(
                 left_tys.push(None);
                 fields.push(DestructureField::None);
             }
+
             Some(pt::Parameter {
                 loc,
                 ty,
                 storage,
                 name: None,
+                annotation,
             }) => {
+                // The grammar does not allow annotation in destructures, so this assertion shall
+                // always be true.
+                assert!(annotation.is_none());
                 if let Some(storage) = storage {
                     diagnostics.push(Diagnostic::error(
                         storage.loc(),
@@ -1563,7 +1583,11 @@ fn destructure(
                 ty,
                 storage,
                 name: Some(name),
+                annotation,
             }) => {
+                // The grammar does not allow annotation in destructures, so this assertion shall
+                // always be true.
+                assert!(annotation.is_none());
                 let (ty, ty_loc) = resolve_var_decl_ty(ty, storage, context, ns, diagnostics)?;
 
                 if let Some(pos) = symtable.add(
@@ -1589,6 +1613,7 @@ fn destructure(
                             readonly: false,
                             infinite_size: false,
                             recursive: false,
+                            annotation: None,
                         },
                     ));
                 }
@@ -2035,8 +2060,12 @@ pub fn parameter_list_to_expr_list<'a>(
                         broken = true;
                     }
                     Some(pt::Parameter {
-                        name: Some(name), ..
+                        name: Some(name),
+                        annotation,
+                        ..
                     }) => {
+                        // The grammar does not allow an annotation inside a list
+                        assert!(annotation.is_none());
                         diagnostics.push(Diagnostic::error(
                             name.loc,
                             "single value expected".to_string(),
@@ -2045,15 +2074,20 @@ pub fn parameter_list_to_expr_list<'a>(
                     }
                     Some(pt::Parameter {
                         storage: Some(storage),
+                        annotation,
                         ..
                     }) => {
+                        // The grammar does not allow an annotation inside a list
+                        assert!(annotation.is_none());
                         diagnostics.push(Diagnostic::error(
                             storage.loc(),
                             "storage specified not permitted here".to_string(),
                         ));
                         broken = true;
                     }
-                    Some(pt::Parameter { ty, .. }) => {
+                    Some(pt::Parameter { annotation, ty, .. }) => {
+                        // The grammar does not allow an annotation inside a list
+                        assert!(annotation.is_none());
                         list.push(ty);
                     }
                 }
@@ -2283,6 +2317,14 @@ fn try_catch(
         let arg_ty = args.remove(0);
 
         match &param.1 {
+            Some(pt::Parameter {
+                annotation: Some(annotation),
+                ..
+            }) => {
+                diagnostics.push(unexpected_parameter_annotation(annotation.loc));
+                broken = true;
+            }
+
             Some(pt::Parameter {
                 ty, storage, name, ..
             }) => {
@@ -2321,6 +2363,7 @@ fn try_catch(
                                 readonly: false,
                                 infinite_size: false,
                                 recursive: false,
+                                annotation: None,
                             },
                         ));
                     }
@@ -2336,6 +2379,7 @@ fn try_catch(
                             readonly: false,
                             infinite_size: false,
                             recursive: false,
+                            annotation: None,
                         },
                     ));
                 }
@@ -2408,6 +2452,8 @@ fn try_catch(
                             ),
                         ));
                         return Err(());
+                    } else if let Some(annotation) = &param.annotation {
+                        diagnostics.push(unexpected_parameter_annotation(annotation.loc));
                     }
 
                     let mut result = Parameter {
@@ -2419,6 +2465,7 @@ fn try_catch(
                         readonly: false,
                         infinite_size: false,
                         recursive: false,
+                        annotation: None,
                     };
 
                     if let Some(name) = &param.name {
@@ -2465,6 +2512,9 @@ fn try_catch(
                         ),
                     ));
                     return Err(());
+                } else if let Some(annotation) = &param.annotation {
+                    ns.diagnostics
+                        .push(unexpected_parameter_annotation(annotation.loc));
                 }
 
                 let (error_ty, ty_loc) =
@@ -2502,6 +2552,7 @@ fn try_catch(
                     readonly: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 };
 
                 if let Some(name) = &param.name {

+ 1 - 0
src/sema/tests/mod.rs

@@ -37,6 +37,7 @@ fn test_statement_reachable() {
                     readonly: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 },
                 None,
             ),

+ 4 - 1
src/sema/types.rs

@@ -633,7 +633,7 @@ fn contract_annotations(
             continue;
         }
 
-        match &note.value {
+        match &note.value.as_ref().unwrap() {
             pt::Expression::StringLiteral(values) if values.len() == 1 => {
                 let string = &values[0].string;
                 let mut loc = values[0].loc;
@@ -751,6 +751,7 @@ pub fn struct_decl(
             readonly: false,
             infinite_size: false,
             recursive: false,
+            annotation: None,
         });
     }
 
@@ -858,6 +859,7 @@ fn event_decl(
             readonly: false,
             infinite_size: false,
             recursive: false,
+            annotation: None,
         });
     }
 
@@ -965,6 +967,7 @@ fn error_decl(
             readonly: false,
             infinite_size: false,
             recursive: false,
+            annotation: None,
         });
     }
 

+ 3 - 0
src/sema/variables.rs

@@ -576,6 +576,7 @@ fn collect_parameters(
                 readonly: false,
                 infinite_size: false,
                 recursive: false,
+                annotation: None,
             });
 
             collect_parameters(value, value_name, symtable, params, expr, ns)
@@ -627,6 +628,7 @@ fn collect_parameters(
                     readonly: false,
                     infinite_size: false,
                     recursive: false,
+                    annotation: None,
                 });
             }
 
@@ -645,6 +647,7 @@ fn collect_parameters(
             readonly: false,
             infinite_size: false,
             recursive: false,
+            annotation: None,
         }),
     }
 }

+ 1 - 0
src/sema/yul/expression.rs

@@ -425,6 +425,7 @@ pub(crate) fn resolve_function_call(
             readonly: false,
             infinite_size: false,
             recursive: false,
+            annotation: None,
         };
 
         for item in &resolved_arguments {

+ 1 - 0
src/sema/yul/functions.rs

@@ -180,6 +180,7 @@ fn process_parameters(parameters: &[pt::YulTypedIdentifier], ns: &mut Namespace)
             readonly: false,
             infinite_size: false,
             recursive: false,
+            annotation: None,
         });
     }
 

+ 2 - 0
src/sema/yul/tests/expression.rs

@@ -734,6 +734,7 @@ fn check_arguments() {
                 readonly: false,
                 infinite_size: false,
                 recursive: false,
+                annotation: None,
             },
             Parameter {
                 loc,
@@ -747,6 +748,7 @@ fn check_arguments() {
                 readonly: false,
                 infinite_size: false,
                 recursive: false,
+                annotation: None,
             },
         ],
     );

+ 1 - 2
tests/codegen_testcases/solidity/solana_payer_account.sol

@@ -17,10 +17,9 @@ contract Builder {
 
 @program_id("SoLGijpEqEeXLEqa9ruh7a6Lu4wogd6rM8FNoR7e3wY")
 contract Built {
-    @seed(my_seed)
     @space(1024)
     @payer(payer_account)
-    constructor(bytes my_seed) {}
+    constructor(@seed bytes my_seed) {}
     // BEGIN-CHECK: solang_dispatch
     // CHECK: ty:struct AccountMeta[2] %metas.temp.10 = [2] [ struct { (load (struct (subscript struct AccountInfo[] (builtin Accounts ())[uint32 1]) field 0)), true, true }, struct { (builtin GetAddress ()), true, true } ]
     // The account metas should have the proper index in the AccountInfo array: 1

+ 2 - 3
tests/contract_testcases/solana/annotations/account_name_collision.sol

@@ -11,10 +11,9 @@ contract Builder {
 
 @program_id("SoLGijpEqEeXLEqa9ruh7a6Lu4wogd6rM8FNoR7e3wY")
 contract BeingBuilt {
-    @seed(my_seed)
     @space(1024)
     @payer(payer_account)
-    constructor(bytes my_seed) {}
+    constructor(@seed bytes my_seed) {}
 
     function say_this(string text) public pure {
         print(text);
@@ -24,4 +23,4 @@ contract BeingBuilt {
 // ---- Expect: diagnostics ----
 // warning: 3:5-21: storage variable 'other' has been assigned, but never read
 // error: 5:5-26: account name collision encountered. Calling a function that requires an account whose name is also defined in the current function will create duplicate names in the IDL. Please, rename one of the accounts
-// 	note 16:5-26: other declaration
+// 	note 15:5-26: other declaration

+ 16 - 0
tests/contract_testcases/solana/annotations/at_whitespace.sol

@@ -0,0 +1,16 @@
+@program_id("SeedHw4CsFsDEGu2AVwFM1toGXsbAJSKnb7kS8TrLxu")
+contract Seed1 {
+
+    @payer(payer)
+    constructor(@ seed bytes seed, @bump bytes1 bump, @space uint64 space) {
+        print("In Seed1 constructor");
+    }
+
+    function say_hello() pure public {
+        print("Hello from Seed1");
+    }
+}
+
+// ---- Expect: diagnostics ----
+// error: 5:17-18: unrecognised token '@'
+// error: 5:24-29: unrecognised token 'bytes', expected "(", ")", "++", ",", "--", ".", "[", "calldata", "case", "default", "leave", "memory", "revert", "storage", "switch", "{", identifier

+ 35 - 26
tests/contract_testcases/solana/annotations/bad_accounts.sol

@@ -1,72 +1,81 @@
 
 contract BeingBuilt1 {
-    @seed(my_seed)
     @space(1024)
     @payer(clock)
-    constructor(bytes my_seed) {}
+    constructor(@seed bytes my_seed) {}
 }
 
 contract BeingBuilt2 {
-    @seed(my_seed)
     @space(1024)
     @payer(systemProgram)
-    constructor(bytes my_seed) {}
+    constructor(@seed bytes my_seed) {}
 }
 
 contract BeingBuilt3 {
-    @seed(my_seed)
     @space(1024)
     @payer(associatedTokenProgram)
-    constructor(bytes my_seed) {}
+    constructor(@seed bytes my_seed) {}
 }
 
 contract BeingBuilt4 {
-    @seed(my_seed)
     @space(1024)
     @payer(rent)
-    constructor(bytes my_seed) {}
+    constructor(@seed bytes my_seed) {}
 }
 
 contract BeingBuilt5 {
-    @seed(my_seed)
     @space(1024)
     @payer(tokenProgram)
-    constructor(bytes my_seed) {}
+    constructor(@seed bytes my_seed) {}
 }
 
 contract BeingBuilt6 {
-    @seed(my_seed)
     @space(1024)
     @payer(dataAccount)
-    constructor(bytes my_seed) {}
+    constructor(@seed bytes my_seed) {}
 }
 
 contract BeingBuilt7 {
-    @seed(my_seed)
-    @space(1024)
     @payer(SysvarInstruction)
-    constructor(bytes my_seed) {}
+    constructor(@seed bytes my_seed, @space uint64 my_space) {}
 }
 
 contract BeingBuilt8 {
-    @seed(my_seed)
+    @seed("pine_tree")
     @space(1024)
     @payer(solang)
     @payer(solang)
-    constructor(bytes my_seed) {}
+    constructor() {}
 
     function say_this(string text) public pure {
         print(text);
     }
 }
 
+
+contract OldAnnotationSyntax {
+    @payer(my_account)
+    @seed(my_seed)
+    @space(my_space)
+    @bump(my_bump)
+    constructor(bytes my_seed, uint64 my_space, bytes1 my_bump) {}
+
+    function my_func(@myNote uint64 a) public pure returns (uint64) {
+        return a-2;
+    }
+}
+
 // ---- Expect: diagnostics ----
-// error: 5:12-17: 'clock' is a reserved account name
-// error: 12:12-25: 'systemProgram' is a reserved account name
-// error: 19:12-34: 'associatedTokenProgram' is a reserved account name
-// error: 26:12-16: 'rent' is a reserved account name
-// error: 33:12-24: 'tokenProgram' is a reserved account name
-// error: 40:12-23: 'dataAccount' is a reserved account name
-// error: 47:12-29: 'SysvarInstruction' is a reserved account name
-// error: 55:12-18: account 'solang' already defined
-// 	note 54:5-19: previous definition
+// error: 4:12-17: 'clock' is a reserved account name
+// error: 10:12-25: 'systemProgram' is a reserved account name
+// error: 16:12-34: 'associatedTokenProgram' is a reserved account name
+// error: 22:12-16: 'rent' is a reserved account name
+// error: 28:12-24: 'tokenProgram' is a reserved account name
+// error: 34:12-23: 'dataAccount' is a reserved account name
+// error: 39:12-29: 'SysvarInstruction' is a reserved account name
+// error: 47:12-18: account 'solang' already defined
+// 	note 46:5-19: previous definition
+// error: 58:11-18: '@seed' annotation on top of a constructor only accepts literals
+// error: 59:12-20: '@space' annotation on top of a constructor only accepts literals
+// error: 60:11-18: '@bump' annotation on top of a constructor only accepts literals
+// error: 63:22-29: parameter annotations are only allowed in constructors

+ 25 - 34
tests/contract_testcases/solana/annotations/constructor_seeds.sol

@@ -1,41 +1,34 @@
 @program_id("@#$!")
 contract c1 {
 	/**
-	 * Multiple seeds allowed, but bump must be after last seed.
+	 * Multiple seeds allowed. Space has an incorrect expression
 	 */
 	@seed("feh")
-	@seed(foo)
-	@seed(bar)
-	@bump(b)
-	@seed(baz)
 	@space(102 + a)
-	constructor(bytes foo, string bar, bytes baz, uint8 b) {}
+	constructor(@seed bytes foo, @seed string bar, @seed bytes baz, @bump uint8 b) {}
 }
 
 @program_id("102")
 contract c2 {
 	/// Only one bump allowed.
 	@seed(hex"41420044")
-	@bump(b)
 	@bump(5)
 	@payer(address"Chi1doxDSNjrmbZ5sq3H2cXyTq3KNfGepmbhyHaxcr")
 	@payer(bar)
 	@space(1025 + 5)
 	@space(4)
-	constructor(bytes foo, address payable bar, bytes baz, uint8 b) {}
+	constructor(bytes foo, bytes baz, @bump uint8 b) {}
 }
 
 @program_id(foo)
 contract c3 {
-	/**  Only one bump allowed. */
+	/**  Only one bump and one space allowed. */
 	@seed(hex"41420044")
-	@bump(b)
-	@bump(5)
 	@payer(my_account)
 	@payer(bar)
 	@space(1025 + 5)
 	@space(4)
-	constructor(bytes foo, address payable bar, bytes baz, uint8 b) {}
+	constructor(bytes foo, address payable bar, bytes baz, @bump bytes1 b, @bump bytes1 c) {}
 
 	@seed("meh")
 	@bump(1)
@@ -54,25 +47,23 @@ contract c4 {
 
 // ---- Expect: diagnostics ----
 // error: 1:14: address literal @#$! invalid character '@'
-// error: 8:8-11: conversion from string to bytes not possible
-// error: 9:2-10: @bump should be after the last @seed
-// 	note 10:2-12: location of @seed annotation
-// error: 11:15-16: 'a' not found
-// error: 15:15: address literal 102 invalid character '0'
-// error: 20:8-9: duplicate @bump annotation for constructor
-// 	note 19:2-10: previous @bump
-// error: 21:2-61: invalid parameter for annotation
-// error: 24:2-11: duplicate @space annotation for constructor
-// 	note 23:2-18: previous @space
-// error: 28:1-17: annotion takes an account, for example '@program_id("BBH7Xi5ddus5EoQhzJLgyodVxJJGkvBRCY5AhBA1jwUr")'
-// error: 33:8-9: duplicate @bump annotation for constructor
-// 	note 32:2-10: previous @bump
-// error: 35:2-13: duplicate @payer annotation for constructor
-// 	note 34:2-20: previous @payer
-// error: 37:2-11: duplicate @space annotation for constructor
-// 	note 36:2-18: previous @space
-// error: 40:2-14: unknown annotation seed for function
-// error: 41:2-10: unknown annotation bump for function
-// error: 42:2-62: unknown annotation payer for function
-// error: 43:2-11: unknown annotation space for function
-// error: 52:2-16: @payer annotation required for constructor
+// error: 7:15-16: 'a' not found
+// error: 8:31-36: conversion from string to bytes not possible
+// error: 11:15: address literal 102 invalid character '0'
+// error: 16:2-61: invalid parameter for annotation
+// error: 19:9-10: duplicate @space annotation for constructor
+// 	note 18:2-18: previous @space
+// error: 20:36-41: duplicate @bump annotation for constructor
+// 	note 15:2-10: previous @bump
+// error: 23:1-17: annotion takes an account, for example '@program_id("BBH7Xi5ddus5EoQhzJLgyodVxJJGkvBRCY5AhBA1jwUr")'
+// error: 28:2-13: duplicate @payer annotation for constructor
+// 	note 27:2-20: previous @payer
+// error: 30:9-10: duplicate @space annotation for constructor
+// 	note 29:2-18: previous @space
+// error: 31:73-78: duplicate @bump annotation for constructor
+// 	note 31:57-62: previous @bump
+// error: 33:2-14: unknown annotation seed for function
+// error: 34:2-10: unknown annotation bump for function
+// error: 35:2-62: unknown annotation payer for function
+// error: 36:2-11: unknown annotation space for function
+// error: 45:2-16: @payer annotation required for constructor

+ 15 - 12
tests/contract_testcases/solana/annotations/constructor_seeds_bad.sol

@@ -5,23 +5,26 @@ contract c1 {
 	@bump(foo)
 	constructor() {}
 
-	// only one bump allowed
+	// only one space allowed
 	@seed("Fickle")
-	@bump(foo)
-	@bump(foo)
-	constructor(bytes1 foo) {}
+	@space(3)
+	constructor(@space uint64 arg1) {}
+
+	// only one bump allowed
+	@seed("Tree")
+	@bump(90)
+	constructor(@bump bytes1 b1) {}
 
 	@seed("feh")
-	@seed(foo)
-	@seed(bar)
-	@seed(baz)
-	@bump(b)
-	constructor(bytes foo, string bar, bytes baz, uint8 b) {}
+	constructor(@seed bytes foo, @seed string bar, @seed bytes baz, @bump uint64 b) {}
 }
 
 // ---- Expect: diagnostics ----
 // error: 4:8-9: 'x' not found
 // error: 5:8-11: 'foo' not found
-// error: 11:8-11: duplicate @bump annotation for constructor
-// 	note 10:2-12: previous @bump
-// error: 16:8-11: conversion from string to bytes not possible
+// error: 11:14-20: duplicate @space annotation for constructor
+// 	note 10:2-11: previous @space
+// error: 16:14-19: duplicate @bump annotation for constructor
+// 	note 15:2-11: previous @bump
+// error: 19:31-36: conversion from string to bytes not possible
+// error: 19:66-71: implicit conversion to bytes1 from uint64 not allowed

+ 44 - 0
tests/contract_testcases/solana/annotations/parameter_annotation.sol

@@ -0,0 +1,44 @@
+contract bar {
+    @space(511 + 102)
+    constructor(@payer address addr) {}
+
+    function hello() public returns (bool) {
+        return true;
+    }
+}
+
+contract beerBar {
+    @bump(52)
+    @payer(addr)
+    constructor(@space uint64 d, @space uint8 f) {}
+
+    function hello() public returns (bool) {
+        return true;
+    }
+}
+
+contract WineBar {
+    @payer(addr)
+    constructor(@space string f) {}
+
+    function hello() public returns (bool) {
+        return true;
+    }
+}
+
+contract SpiritBar {
+    @payer(addr)
+    constructor(@other string f) {}
+
+    function hello(@item uint32 c) public returns (bool) {
+        return c==0;
+    }
+}
+
+// ---- Expect: diagnostics ----
+// error: 3:17-23: @payer annotation not allowed next to a parameter
+// error: 13:34-40: duplicate @space annotation for constructor
+// 	note 13:17-23: previous @space
+// error: 22:17-23: conversion from string to uint64 not possible
+// error: 31:17-23: unknown annotation other for constructor
+// error: 33:20-25: parameter annotations are only allowed in constructors

+ 0 - 0
tests/contract_testcases/substrate/constructor_seeds.sol → tests/contract_testcases/substrate/annotations/constructor_seeds.sol


+ 0 - 0
tests/contract_testcases/substrate/selector_override.sol → tests/contract_testcases/substrate/annotations/selector_override.sol


+ 0 - 0
tests/contract_testcases/substrate/selector_override_inherited.sol → tests/contract_testcases/substrate/annotations/selector_override_inherited.sol


+ 0 - 0
tests/contract_testcases/substrate/solana_seeds.sol → tests/contract_testcases/substrate/annotations/solana_seeds.sol


+ 76 - 0
tests/contract_testcases/substrate/annotations/unexpected_annotations.sol

@@ -0,0 +1,76 @@
+function free_standing(@item string a) returns (uint32) {
+    return a.length;
+}
+
+function free_standing2(string a) returns (@ret uint32 ret) {
+    ret = a.length;
+}
+
+
+contract MyTest {
+    constructor() {}
+
+    modifier MyMod(@item1 string c) {
+        _;
+    }
+
+    function test1(@ann bytes one) public pure returns (uint32) {
+        return one.length;
+    }
+
+    function test2(bytes one) public pure returns (@um uint32 item, @hm uint256) {
+        return (one.length, 36);
+    }
+
+    function test3() external pure returns (uint32, int8) {
+        return (34, 21);
+    }
+
+    function test4() internal pure returns (uint32, int8) {
+        try this.test3() returns (@item uint32 a, int8 b) {
+            return (a, b);
+        } catch (@item2 bytes err) {
+            return (0,0);
+        }
+    }
+
+    function test5() internal pure returns (uint32, int8) {
+        try this.test3() returns (uint32 a, int8 b) {
+            return (a, b);
+        } catch (@item2 bytes err) {
+            return (0,0);
+        }
+    }
+
+    function test6() internal pure returns (uint32, int8) {
+        try this.test3() returns (uint32 a, int8 b) {
+            return (a, b);
+        } catch Error (@item2 string err) {
+            return (0,0);
+        }
+    }
+
+    function test7() public pure returns (uint32) {
+        function(@item1 bytes bb) returns (int32) fPtr = test1;
+        return fPtr("bc");
+    }
+
+    function test8() public pure returns (uint32, int8) {
+        function() external returns (@smt uint32, @utm int8) fPtr = this.test3;
+        return fPtr();
+    }
+}
+
+// ---- Expect: diagnostics ----
+// error: 1:24-29: unexpected parameter annotation
+// error: 5:44-48: unexpected parameter annotation
+// error: 13:20-26: unexpected parameter annotation
+// error: 17:20-24: unexpected parameter annotation
+// error: 21:52-55: unexpected parameter annotation
+// error: 21:69-72: unexpected parameter annotation
+// error: 30:35-40: unexpected parameter annotation
+// error: 40:18-24: unexpected parameter annotation
+// error: 48:24-30: unexpected parameter annotation
+// error: 54:18-24: unexpected parameter annotation
+// error: 59:38-42: unexpected parameter annotation
+// error: 59:51-55: unexpected parameter annotation

+ 6 - 15
tests/solana_tests/create_contract.rs

@@ -325,9 +325,8 @@ fn account_with_space() {
         r#"
         contract bar {
 
-            @space(102 * x)
             @payer(payer)
-            constructor(uint64 x, address payer) {}
+            constructor(@space uint64 x, address payer) {}
 
             function hello() public returns (bool) {
                 return true;
@@ -345,12 +344,12 @@ fn account_with_space() {
     vm.constructor(&[
         BorshToken::Uint {
             width: 64,
-            value: 3.into(),
+            value: 306.into(),
         },
         BorshToken::Address(payer),
     ]);
 
-    assert_eq!(vm.account_data.get_mut(&data).unwrap().data.len(), 3 * 102);
+    assert_eq!(vm.account_data.get_mut(&data).unwrap().data.len(), 306);
 
     let ret = vm.function("hello", &[]).unwrap();
 
@@ -364,9 +363,8 @@ fn account_with_seed() {
         contract bar {
 
             @space(511 + 102)
-            @seed(seed)
             @payer(payer)
-            constructor(bytes seed, address payer) {}
+            constructor(@seed bytes seed) {}
 
             function hello() public returns (bool) {
                 return true;
@@ -381,9 +379,7 @@ fn account_with_seed() {
 
     vm.stack[0].data = seed.0;
 
-    let payer = account_new();
-
-    vm.constructor(&[BorshToken::Bytes(seed.1), BorshToken::Address(payer)]);
+    vm.constructor(&[BorshToken::Bytes(seed.1)]);
 
     assert_eq!(
         vm.account_data.get_mut(&seed.0).unwrap().data.len(),
@@ -402,10 +398,8 @@ fn account_with_seed_bump() {
         contract bar {
 
             @space(511 + 102)
-            @seed(seed)
-            @bump(b)
             @payer(payer)
-            constructor(bytes seed, address payer, byte b) {}
+            constructor(@seed bytes seed, @bump byte b) {}
 
             function hello() public returns (bool) {
                 return true;
@@ -422,11 +416,8 @@ fn account_with_seed_bump() {
 
     vm.stack[0].data = seed.0;
 
-    let payer = account_new();
-
     vm.constructor(&[
         BorshToken::Bytes(seed.1),
-        BorshToken::Address(payer),
         BorshToken::Uint {
             width: 8,
             value: bump.into(),