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 16, 2023
1 parent 92a6c6c commit 07bc473
Show file tree
Hide file tree
Showing 21 changed files with 354 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))
} 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
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 = 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,
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,
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, .. }
| 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
20 changes: 20 additions & 0 deletions src/emit/expression.rs
Expand Up @@ -1428,6 +1428,26 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
bin.vector_new(size, elem_size, initializer.as_ref()).into()
}
}
Expression::FromBufferPointer { ptr, size, .. } => {
// TODO: convert to slice if possible (requires analysis to see if this is possible)
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 07bc473

Please sign in to comment.