-
Notifications
You must be signed in to change notification settings - Fork 77
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
handle_default() implementation without generics/inference #153
Comments
@denismerigoux just suggested on Twitter via DM to inline the implementation of |
You can inline it either directly at the level of your specific backend or it could be inlined on demand (with a CLI flag) during the translation from default calculus to lambda calculus. |
I'm not sure how I would handle all the possible (parameter) permutations at the top level. Do you suggest to generate a function overload for each possible type? |
If you inline it directly in your backend generation, you can turn let exception_acc = None in
let current_exception = match Some (try e1) with EmptyError -> None
let exception_acc = match exception_acc, current_exception with
| None, x -> x
| Some x, None -> Some x
| Some _, Some _ -> panic!
in
(* same thing with e2 and e3 *) ...
match exception_acc with
| Some x -> x
| None -> if e just then e_cons else raise EmptyError |
The problem is then to have the "option" idiom ( I tried to do something like this: struct T { bool __fixme; }
error ConflictError();
error EmptyError();
function handle_default(
function() returns (T memory)[] calldata exceptions,
function() returns (T memory) just,
function() returns (T memory) cons
) pure returns (T memory)
{
T memory acc;
bool acc_is_none = true;
for (uint i = 0; i < exceptions.length; i++) {
T memory new_val;
bool new_val_is_none = true;
try exceptions[i]() returns (T memory val) {
new_val = val;
new_val_is_none = false;
} catch {
new_val_is_none = true;
}
if (acc_is_none) {
acc = new_val;
} else if (!acc_is_none && new_val_is_none) {
// pass
} else if (!acc_is_none && !new_val_is_none) {
revert ConflictError();
}
}
if (acc_is_none) {
if (just()) {
return cons();
} else {
revert EmptyError();
}
} else {
return acc;
}
} But I got two errors:
That's a very big limitation on
https://docs.soliditylang.org/en/develop/control-structures.html#external-function-calls
The |
@denismerigoux so my conclusion is that even if we do inline So we're back to square one. |
What you can't do |
Why do your functions return |
@denismerigoux I know, but the Solidity compiler was complaining about @denismerigoux what are the types held by an option/the template type here? Here is an example of the generated Solidity code: Any local_var_7;
function local_var_7(Any _) {
return individual_2(Unit());
}
function local_var_9(Any _) {
return true;
}
Any local_var_9;
function local_var_11(Any _) {
function local_var_13(Any _) {
return false;
}
Any local_var_13;
function local_var_15(Any _) {
revert EmptyError;
}
Any local_var_15;
return handle_default([], local_var_13, local_var_15);
} If we forget about the
or can it be other types as well (such as a function, or a |
Yeah |
@denismerigoux what do you think about trying to implement function handle_default(
function() returns (T memory)[] calldata exceptions,
function() returns (bool, T memory) just,
function() returns (T memory) cons
) pure returns (T memory)
{
// ...
if (acc_is_none) {
bool is_some;
T memory val;
(is_some, val) = just();
if (!is_some) {
return cons();
} else {
revert EmptyError();
}
} else {
return acc;
}
} The Solidity compiler is fine with that part. Now if that works I have to deal with the remaining "external function call" problem (cf #153 (comment))... |
Yeah it sounds it could work. But you have to carefully review your backend translation, and these sorts of workarounds could introduce subtle bugs. The Solidity typechecking seems very weird from what you're experiencing. |
@denismerigoux are you referring to the
|
Potentially related issues:
We might be able to implement exceptions in a more suitable way using the Yul assembly language: |
Possible workaround using tuple return values: https://ethereum.stackexchange.com/a/78563 pragma solidity ^0.5.0;
import "./Token.sol";
/**
* @dev This contract showcases a simple Try-catch call in Solidity
*/
contract Example {
Token public token;
uint256 public lastAmount;
constructor(Token _token) public {
token = _token;
}
event TransferFromFailed(uint256 _amount);
function tryTransferFrom(address _from, address _to, uint256 _amount) public returns(bool returnedBool, uint256 returnedAmount) {
lastAmount = _amount; // We can query this after transferFrom reverts to confirm that the whole transaction did NOT revert
// and the changes we made to the state are still present.
(bool success, bytes memory returnData) =
address(token).call( // This creates a low level call to the token
abi.encodePacked( // This encodes the function to call and the parameters to pass to that function
token.transferFrom.selector, // This is the function identifier of the function we want to call
abi.encode(_from, _to, _amount) // This encodes the parameter we want to pass to the function
)
);
if (success) { // transferFrom completed successfully (did not revert)
(returnedBool, returnedAmount) = abi.decode(returnData, (bool, uint256));
} else { // transferFrom reverted. However, the complete tx did not revert and we can handle the case here.
// I will emit an event here to show this
emit TransferFromFailed(_amount);
}
}
} |
pragma solidity <0.6.0;
contract OldTryCatch {
function execute(uint256 amount) external {
// the low level call will return `false` if its execution reverts
(bool success, bytes memory returnData) = address(this).call(
abi.encodeWithSignature(
"onlyEven(uint256)",
amount
)
);
if (success) {
// handle success
} else {
// handle exception
}
}
function onlyEven(uint256 a) public {
// Code that can revert
require(a % 2 == 0, "Ups! Reverting");
// ...
}
}
|
We can emulate a more classic bool success;
do // try
{
(success, bytes memory returnData) = address(this).call(
abi.encodeWithSignature(
"onlyEven(uint256)",
amount
)
);
if (!success) {
break;
}
}
while (false)
if (!success) // catch
{
// ...
} |
This looks absolutely horrible, at least I wouldn't trust this in a smart contract :) There is an intern that started this month and who's working on replacing exceptions in the generated code with more classic |
Yep. More of a "proof of concept" than a production solution for sure.
@denismerigoux sounds perfect! Removing the need for exceptions is a big step forward in lowering the target language feature-set requirements. Any chance we can have an issue/PR to follow that progress? Maybe even test it? |
You should lookout for a PR from @lIlIlIlIIIIlIIIllIIlIllIIllIII in the coming weeks. I'll tag it here once it appears. |
@denismerigoux If #158 implements the necessary changes, can we close this issue? |
@denismerigoux correct me if I'm wrong but it looks like Catala still heavily relies on exceptions despite #158 . Here is an example taken from def enfant_le_plus_age(enfant_le_plus_age_in:EnfantLePlusAgeIn):
enfants = enfant_le_plus_age_in.enfants_in
try:
def temp_le_plus_age(_:Unit):
def temp_le_plus_age_1(potentiel_plus_age_1:Enfant, potentiel_plus_age_2:Enfant):
def temp_le_plus_age_2(potentiel_plus_age:Enfant):
return potentiel_plus_age.date_de_naissance
def temp_le_plus_age_3(potentiel_plus_age_1:Enfant):
return potentiel_plus_age_1.date_de_naissance
if (temp_le_plus_age_3(potentiel_plus_age_1) <
temp_le_plus_age_2(potentiel_plus_age_2)):
return potentiel_plus_age_1
else:
return potentiel_plus_age_2
return list_reduce(temp_le_plus_age_1,
Enfant(identifiant = integer_of_string("-1"),
obligation_scolaire = SituationObligationScolaire(SituationObligationScolaire_Code.Pendant,
Unit()),
remuneration_mensuelle = money_of_cents_string("0"),
date_de_naissance = date_of_numbers(2999,12,31),
prise_en_charge = PriseEnCharge(PriseEnCharge_Code.EffectiveEtPermanente,
Unit()),
a_deja_ouvert_droit_aux_allocations_familiales = False,
beneficie_titre_personnel_aide_personnelle_logement = False),
enfants)
def temp_le_plus_age_4(_:Unit):
return True
temp_le_plus_age_5 = handle_default(SourcePosition(filename="examples/allocations_familiales/prologue.catala_fr",
start_line=80, start_column=12,
end_line=80, end_column=23,
law_headings=["Allocations familiales",
"Champs d'applications",
"Prologue"]), [],
temp_le_plus_age_4,
temp_le_plus_age)
except EmptyError:
temp_le_plus_age_5 = dead_value
raise NoValueProvided(SourcePosition(filename="examples/allocations_familiales/prologue.catala_fr",
start_line=80, start_column=12,
end_line=80, end_column=23,
law_headings=["Allocations familiales",
"Champs d'applications",
"Prologue"]))
le_plus_age = temp_le_plus_age_5
return EnfantLePlusAge(le_plus_age = le_plus_age) |
Exceptions are apparently elided/removed when using the But then the
|
@denismerigoux after looking at the stack trace, it looks like this is not specific to the |
@adelaett another thing for you to debug :) |
Hi,
I'm writing a Solidity backend to create smart contracts from the Catala code (cf #143).
One of the issues I'm facing is the implementation of
handle_default()
in the Solidity runtime.The OCaml implementation relies on type inference. The Python implementation relies on generics.
Thus, target languages that have neither type inference nor generics cannot implement
handle_default()
.Is there any way to workaround this?
address(this0.call(...)
to emulatetry
/catch
usingif
.handle_default()
.The text was updated successfully, but these errors were encountered: