Skip to content

Commit

Permalink
Various fallback improvements
Browse files Browse the repository at this point in the history
Allow the following:

	contract A {
		// Take raw calldata as argument and return return data
		fallback(bytes calldata input) external payable (bytes memory) {}
	}

Allow a function to be called fallback:

	contract A {
		// Will give a warning that's not a fallback function,
		// just a regular functon
		function fallback() public {}
	}

Signed-off-by: Sean Young <sean@mess.org>
  • Loading branch information
seanyoung committed Dec 21, 2023
1 parent 1474831 commit 878a85a
Show file tree
Hide file tree
Showing 24 changed files with 390 additions and 81 deletions.
3 changes: 2 additions & 1 deletion docs/examples/polkadot/function_fallback_and_receive.sol
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
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),
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
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
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
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))

Check warning on line 925 in src/codegen/cfg.rs

View check run for this annotation

Codecov / codecov/patch

src/codegen/cfg.rs#L923-L925

Added lines #L923 - L925 were not covered by tests
} else {
ty.to_string(ns)

Check warning on line 927 in src/codegen/cfg.rs

View check run for this annotation

Codecov / codecov/patch

src/codegen/cfg.rs#L927

Added line #L927 was not covered by tests
};

format!(
"(alloc {} ptr {} size {})",
ty,
self.expr_to_string(contract, ns, ptr),
self.expr_to_string(contract, ns, size)
)

Check warning on line 935 in src/codegen/cfg.rs

View check run for this annotation

Codecov / codecov/patch

src/codegen/cfg.rs#L930-L935

Added lines #L930 - L935 were not covered by tests
}
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
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
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
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
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, .. }

Check warning on line 717 in src/codegen/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/codegen/mod.rs#L717

Added line #L717 was not covered by tests
| 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, .. }

Check warning on line 860 in src/codegen/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/codegen/mod.rs#L860

Added line #L860 was not covered by tests
| Expression::BytesCast { ty, .. }
| Expression::RationalNumberLiteral { ty, .. }
| Expression::Subscript { ty, .. }
Expand Down
19 changes: 19 additions & 0 deletions src/emit/expression.rs
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

0 comments on commit 878a85a

Please sign in to comment.