Skip to content
Open
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
3 changes: 2 additions & 1 deletion docs/examples/polkadot/function_fallback_and_receive.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ contract test {
bar = x;
}

fallback() external {
fallback(bytes calldata input) external returns (bytes memory ret) {
// execute if function selector does not match "foo(uint32)" and no value sent
ret = "testdata";
}

receive() external payable {
Expand Down
9 changes: 7 additions & 2 deletions docs/language/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -347,11 +347,11 @@ The difference to a regular ``call`` is that ``delegatecall`` executes the call
* ``value`` can't be specified for ``delegatecall``; instead it will always stay the same in the callee.
* ``msg.sender`` does not change; it stays the same as in the callee.

Refer to the `contracts pallet <https://docs.rs/pallet-contracts/latest/pallet_contracts/api_doc/trait.Version0.html#tymethod.delegate_call>`_
Refer to the `contracts pallet <https://docs.rs/pallet-contracts/latest/pallet_contracts/api_doc/trait.Version0.html#tymethod.delegate_call>`_
and `Ethereum Solidity <https://docs.soliditylang.org/en/latest/introduction-to-smart-contracts.html#delegatecall-and-libraries>`_
documentations for more information.

``delegatecall`` is commonly used to implement re-usable libraries and
``delegatecall`` is commonly used to implement re-usable libraries and
`upgradeable contracts <https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable>`_.

.. code-block:: solidity
Expand Down Expand Up @@ -384,6 +384,11 @@ is executed. This made clear in the declarations; ``receive()`` must be declared
with value and no ``receive()`` function is defined, then the call reverts, likewise if
call is made without value and no ``fallback()`` is defined, then the call also reverts.

The fallback function can defined in two ways. First, it can have no parameters or return
values. Alternatively, it must have a ``bytes`` parameter and ``bytes`` return value. In this
case, the parameter contains the undecoded input (also known as calldata or instruction data),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undecoded = encoded?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording is not great. What I mean is the raw bytes data before going through borsh/scale/eth decoding.

and the return value is the raw return data for the contact.

Both functions must be declared ``external``.

.. include:: ../examples/polkadot/function_fallback_and_receive.sol
Expand Down
2 changes: 2 additions & 0 deletions solang-parser/src/solidity.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ SolIdentifier: Identifier = {
<l:@L> "case" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "case".to_string()},
<l:@L> "default" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "default".to_string()},
<l:@L> "revert" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "revert".to_string()},
<l:@L> "fallback" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "fallback".to_string()},
<l:@L> "receive" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "receive".to_string()},
}

SolAnnotation: Identifier = {
Expand Down
12 changes: 6 additions & 6 deletions solang-parser/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ contract 9c {
Diagnostic { loc: File(0, 17, 21), level: Error, ty: ParserError, message: "'frum' found where 'from' expected".to_string(), notes: vec![]},
Diagnostic { loc: File(0, 48, 49), level: Error, ty: ParserError, message: "unrecognised token ';', expected \"*\", \"<\", \"<=\", \"=\", \">\", \">=\", \"^\", \"~\", identifier, number, string".to_string(), notes: vec![] },
Diagnostic { loc: File(0, 62, 65), level: Error, ty: ParserError, message: r#"unrecognised token 'for', expected "(", ";", "=""#.to_string(), notes: vec![] },
Diagnostic { loc: File(0, 78, 79), level: Error, ty: ParserError, message: r#"unrecognised token '9', expected "case", "default", "leave", "revert", "switch", identifier"#.to_string(), notes: vec![] },
Diagnostic { loc: File(0, 95, 96), level: Error, ty: ParserError, message: "unrecognised token '0', expected \"(\", \"++\", \"--\", \".\", \"[\", \"case\", \"constant\", \"default\", \"external\", \"immutable\", \"internal\", \"leave\", \"override\", \"private\", \"public\", \"revert\", \"switch\", \"{\", identifier".to_string(), notes: vec![] },
Diagnostic { loc: File(0, 78, 79), level: Error, ty: ParserError, message: r#"unrecognised token '9', expected "case", "default", "fallback", "leave", "receive", "revert", "switch", identifier"#.to_string(), notes: vec![] },
Diagnostic { loc: File(0, 95, 96), level: Error, ty: ParserError, message: "unrecognised token '0', expected \"(\", \"++\", \"--\", \".\", \"[\", \"case\", \"constant\", \"default\", \"external\", \"fallback\", \"immutable\", \"internal\", \"leave\", \"override\", \"private\", \"public\", \"receive\", \"revert\", \"switch\", \"{\", identifier".to_string(), notes: vec![] },
Diagnostic { loc: File(0, 116, 123), level: Error, ty: ParserError, message: "unrecognised token 'uint256', expected \"++\", \"--\", \".\", \"[\", \"case\", \"default\", \"leave\", \"switch\", identifier".to_string(), notes: vec![] },
Diagnostic { loc: File(0, 403, 404), level: Error, ty: ParserError, message: "unrecognised token '3', expected \"(\", \"++\", \"--\", \".\", \"[\", \"case\", \"constant\", \"default\", \"external\", \"immutable\", \"internal\", \"leave\", \"override\", \"private\", \"public\", \"revert\", \"switch\", \"{\", identifier".to_string(), notes: vec![] },
Diagnostic { loc: File(0, 441, 442), level: Error, ty: ParserError, message: r#"unrecognised token '4', expected "(", "case", "default", "leave", "revert", "switch", identifier"#.to_string(), notes: vec![] },
Diagnostic { loc: File(0, 460, 461), level: Error, ty: ParserError, message: "unrecognised token '!', expected \";\", \"case\", \"constant\", \"default\", \"external\", \"immutable\", \"internal\", \"leave\", \"override\", \"payable\", \"private\", \"public\", \"pure\", \"return\", \"returns\", \"revert\", \"switch\", \"view\", \"virtual\", \"{\", identifier".to_string(), notes: vec![] },
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, 403, 404), level: Error, ty: ParserError, message: "unrecognised token '3', expected \"(\", \"++\", \"--\", \".\", \"[\", \"case\", \"constant\", \"default\", \"external\", \"fallback\", \"immutable\", \"internal\", \"leave\", \"override\", \"private\", \"public\", \"receive\", \"revert\", \"switch\", \"{\", identifier".to_string(), notes: vec![] },
Diagnostic { loc: File(0, 441, 442), level: Error, ty: ParserError, message: r#"unrecognised token '4', expected "(", "case", "default", "fallback", "leave", "receive", "revert", "switch", identifier"#.to_string(), notes: vec![] },
Diagnostic { loc: File(0, 460, 461), level: Error, ty: ParserError, message: "unrecognised token '!', expected \";\", \"case\", \"constant\", \"default\", \"external\", \"fallback\", \"immutable\", \"internal\", \"leave\", \"override\", \"payable\", \"private\", \"public\", \"pure\", \"receive\", \"return\", \"returns\", \"revert\", \"switch\", \"view\", \"virtual\", \"{\", identifier".to_string(), notes: vec![] },
Diagnostic { loc: File(0, 482, 483), level: Error, ty: ParserError, message: "unrecognised token '3', expected \"!=\", \"%\", \"%=\", \"&\", \"&&\", \"&=\", \"(\", \"*\", \"**\", \"*=\", \"+\", \"++\", \"+=\", \"-\", \"--\", \"-=\", \".\", \"/\", \"/=\", \";\", \"<\", \"<<\", \"<<=\", \"<=\", \"=\", \"==\", \">\", \">=\", \">>\", \">>=\", \"?\", \"[\", \"^\", \"^=\", \"calldata\", \"case\", \"default\", \"fallback\", \"leave\", \"memory\", \"receive\", \"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, annotation, hexnumber, hexstring, identifier, number, rational, string".to_string(), notes: vec![] }
Expand Down
14 changes: 14 additions & 0 deletions src/codegen/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,20 @@ impl ControlFlowGraph {
}
)
}
Expression::FromBufferPointer { ty, ptr, size, .. } => {
let ty = if let Type::Slice(ty) = ty {
format!("slice {}", ty.to_string(ns))
} else {
ty.to_string(ns)
};

format!(
"(alloc {} ptr {} size {})",
ty,
self.expr_to_string(contract, ns, ptr),
self.expr_to_string(contract, ns, size)
)
}
Expression::StringCompare { left, right, .. } => format!(
"(strcmp ({}) ({}))",
self.location_to_string(contract, ns, left),
Expand Down
9 changes: 9 additions & 0 deletions src/codegen/constant_folding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,15 @@ fn expression(
},
false,
),
Expression::FromBufferPointer { loc, ty, ptr, size } => (
Expression::FromBufferPointer {
loc: *loc,
ty: ty.clone(),
ptr: Box::new(expression(ptr, vars, cfg, ns).0),
size: Box::new(expression(size, vars, cfg, ns).0),
},
false,
),

Expression::NumberLiteral { .. }
| Expression::RationalNumberLiteral { .. }
Expand Down
80 changes: 62 additions & 18 deletions src/codegen/dispatch/polkadot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,24 +465,68 @@ impl<'a> Dispatch<'a> {

self.cfg.set_basic_block(fallback_block);
if let Some(cfg_no) = fallback_cfg {
self.add(Instr::Call {
res: vec![],
return_tys: vec![],
call: InternalCallTy::Static { cfg_no },
args: vec![],
});
let data_len = Expression::NumberLiteral {
loc: Codegen,
ty: Uint(32),
value: 0.into(),
};
let data = Expression::AllocDynamicBytes {
loc: Codegen,
ty: Type::DynamicBytes,
size: data_len.clone().into(),
initializer: None,
};
self.add(Instr::ReturnData { data, data_len })
if self.all_cfg[cfg_no].params.is_empty() {
self.add(Instr::Call {
res: vec![],
return_tys: vec![],
call: InternalCallTy::Static { cfg_no },
args: vec![],
});
let data_len = Expression::NumberLiteral {
loc: Codegen,
ty: Uint(32),
value: 0.into(),
};
let data = Expression::AllocDynamicBytes {
loc: Codegen,
ty: Type::DynamicBytes,
size: data_len.clone().into(),
initializer: None,
};
self.add(Instr::ReturnData { data, data_len })
} else {
let arg = Expression::FromBufferPointer {
loc: Codegen,
ty: Type::DynamicBytes,
ptr: Expression::FunctionArg {
loc: Codegen,
ty: Type::BufferPointer,
arg_no: 0,
}
.into(),
size: Expression::Variable {
loc: Codegen,
ty: Type::Uint(32),
var_no: self.input_len,
}
.into(),
};

let var_no = self
.vartab
.temp_name("fallback_return_data", &Type::DynamicBytes);

self.add(Instr::Call {
res: vec![var_no],
return_tys: vec![Type::DynamicBytes],
call: InternalCallTy::Static { cfg_no },
args: vec![arg],
});

let data = Expression::Variable {
loc: Codegen,
ty: Type::DynamicBytes,
var_no,
};
let data_len = Expression::Builtin {
loc: Codegen,
tys: vec![Type::Uint(32)],
kind: Builtin::ArrayLength,
args: vec![data.clone()],
};

self.add(Instr::ReturnData { data, data_len });
}
} else {
self.selector_invalid();
}
Expand Down
75 changes: 55 additions & 20 deletions src/codegen/dispatch/solana.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ pub(crate) fn function_dispatch(
],
};

let argsdata = Expression::AdvancePointer {
pointer: Box::new(argsdata),
let argsdata_after_discriminator = Expression::AdvancePointer {
pointer: argsdata.clone().into(),
bytes_offset: Box::new(Expression::NumberLiteral {
loc: Loc::Codegen,
ty: Type::Uint(32),
Expand Down Expand Up @@ -157,7 +157,7 @@ pub(crate) fn function_dispatch(
add_function_dispatch_case(
cfg_no,
func_cfg,
&argsdata,
&argsdata_after_discriminator,
argslen.clone(),
contract_no,
ns,
Expand All @@ -168,7 +168,7 @@ pub(crate) fn function_dispatch(
add_constructor_dispatch_case(
contract_no,
cfg_no,
&argsdata,
&argsdata_after_discriminator,
argslen.clone(),
func_cfg,
ns,
Expand Down Expand Up @@ -235,22 +235,57 @@ pub(crate) fn function_dispatch(
check_magic(ns.contracts[contract_no].selector(), &mut cfg, &mut vartab);
}

cfg.add(
&mut vartab,
Instr::Call {
res: vec![],
return_tys: vec![],
args: vec![],
call: InternalCallTy::Static { cfg_no },
},
);

cfg.add(
&mut vartab,
Instr::ReturnCode {
code: ReturnCode::Success,
},
);
if all_cfg[cfg_no].params.len() == 1 {
let arg = Expression::FromBufferPointer {
loc: Loc::Codegen,
ty: Type::DynamicBytes,
ptr: argsdata.into(),
size: argslen.into(),
};

let var_no = vartab.temp_name("fallback_return_data", &Type::DynamicBytes);

cfg.add(
&mut vartab,
Instr::Call {
res: vec![var_no],
return_tys: vec![Type::DynamicBytes],
call: InternalCallTy::Static { cfg_no },
args: vec![arg],
},
);

let data = Expression::Variable {
loc: Loc::Codegen,
ty: Type::DynamicBytes,
var_no,
};
let data_len = Expression::Builtin {
loc: Loc::Codegen,
tys: vec![Type::Uint(32)],
kind: Builtin::ArrayLength,
args: vec![data.clone()],
};

cfg.add(&mut vartab, Instr::ReturnData { data, data_len });
} else {
cfg.add(
&mut vartab,
Instr::Call {
res: vec![],
return_tys: vec![],
args: vec![],
call: InternalCallTy::Static { cfg_no },
},
);

cfg.add(
&mut vartab,
Instr::ReturnCode {
code: ReturnCode::Success,
},
);
}
}
None => {
cfg.add(
Expand Down
8 changes: 8 additions & 0 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,12 @@ pub enum Expression {
size: Box<Expression>,
initializer: Option<Vec<u8>>,
},
FromBufferPointer {
loc: pt::Loc,
ty: Type,
ptr: Box<Expression>,
size: Box<Expression>,
},
ArrayLiteral {
loc: pt::Loc,
ty: Type,
Expand Down Expand Up @@ -708,6 +714,7 @@ impl CodeLocation for Expression {
| Expression::ShiftLeft { loc, .. }
| Expression::RationalNumberLiteral { loc, .. }
| Expression::AllocDynamicBytes { loc, .. }
| Expression::FromBufferPointer { loc, .. }
| Expression::BytesCast { loc, .. }
| Expression::More { loc, .. }
| Expression::ZeroExt { loc, .. } => *loc,
Expand Down Expand Up @@ -850,6 +857,7 @@ impl RetrieveType for Expression {
| Expression::StructMember { ty, .. }
| Expression::FunctionArg { ty, .. }
| Expression::AllocDynamicBytes { ty, .. }
| Expression::FromBufferPointer { ty, .. }
| Expression::BytesCast { ty, .. }
| Expression::RationalNumberLiteral { ty, .. }
| Expression::Subscript { ty, .. }
Expand Down
19 changes: 19 additions & 0 deletions src/emit/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1428,6 +1428,25 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
bin.vector_new(size, elem_size, initializer.as_ref()).into()
}
}
Expression::FromBufferPointer { ptr, size, .. } => {
let ptr = expression(target, bin, ptr, vartab, function, ns).into_pointer_value();
let elem_size = i32_const!(1);
let size = bin.builder.build_int_truncate(
expression(target, bin, size, vartab, function, ns).into_int_value(),
bin.context.i32_type(),
"",
);

bin.builder
.build_call(
bin.module.get_function("vector_new").unwrap(),
&[size.into(), elem_size.into(), ptr.into()],
"",
)
.try_as_basic_value()
.left()
.unwrap()
}
Expression::Builtin {
kind: Builtin::ArrayLength,
args,
Expand Down
Loading