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

Refactor constructor annotations #1366

Merged
merged 4 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions docs/examples/solana/constructor_annotations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
// ...
}
}
3 changes: 1 addition & 2 deletions docs/examples/solana/payer_annotation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 5 additions & 7 deletions docs/language/contracts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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``:

Expand All @@ -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
-------------------------------------------------
Expand Down
19 changes: 12 additions & 7 deletions docs/targets/solana.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:

Expand Down Expand Up @@ -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
_________________________
Expand Down
9 changes: 2 additions & 7 deletions integration/solana/create_contract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand All @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion integration/solana/create_contract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'));

Expand Down
31 changes: 27 additions & 4 deletions solang-parser/src/helpers/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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),* $(,)?) => {
Expand Down Expand Up @@ -1411,6 +1417,7 @@ mod tests {
ty: expr_ty!($i),
storage: None,
name: None,
annotation: None,
}
};

Expand All @@ -1420,6 +1427,7 @@ mod tests {
ty: expr_ty!($i),
storage: None,
name: Some(id(stringify!($n))),
annotation: None,
}
};

Expand All @@ -1429,6 +1437,7 @@ mod tests {
ty: expr_ty!($i),
storage: Some(storage!($s)),
name: Some(id(stringify!($n))),
annotation: None,
}
};
}
Expand Down Expand Up @@ -1496,7 +1505,7 @@ mod tests {
struct_tests![
pt::Annotation {
id: id("name"),
value: expr!(value),
value: Some(expr!(value)),
} => "@name(value)",

pt::Base {
Expand Down Expand Up @@ -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,
Expand Down
98 changes: 67 additions & 31 deletions solang-parser/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ pub enum Token<'input> {
Case,
Default,
YulArrow,
At,

Annotation(&'input str),
}

impl<'input> fmt::Display for Token<'input> {
Expand Down Expand Up @@ -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}"),
}
}
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -974,27 +960,37 @@ 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)),
Some((i, ')')) => return Some((i, Token::CloseParenthesis, i + 1)),
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();
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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());
}
}
Loading