From 2d0f87a70927dee837be67bf112121f642de728c Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 7 Aug 2024 15:31:09 +0300 Subject: [PATCH] Syntax for parsing Rust --- Makefile | 26 + rust-semantics/rust.md | 8 + rust-semantics/syntax.md | 1040 ++++++++++++++++++++++++++++ tests/syntax/erc_20_token.rs | 150 ++++ tests/syntax/lending.rs | 409 +++++++++++ tests/syntax/staking.rs | 93 +++ tests/syntax/uniswap_v_2_pair.rs | 142 ++++ tests/syntax/uniswap_v_2_router.rs | 290 ++++++++ tests/syntax/uniswap_v_2_swap.rs | 177 +++++ 9 files changed, 2335 insertions(+) create mode 100644 Makefile create mode 100644 rust-semantics/rust.md create mode 100644 rust-semantics/syntax.md create mode 100644 tests/syntax/erc_20_token.rs create mode 100644 tests/syntax/lending.rs create mode 100644 tests/syntax/staking.rs create mode 100644 tests/syntax/uniswap_v_2_pair.rs create mode 100644 tests/syntax/uniswap_v_2_router.rs create mode 100644 tests/syntax/uniswap_v_2_swap.rs diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6c3ff2b --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +SEMANTICS_FILES ::= $(shell find rust-semantics/ -type f -name '*') +RUST_KOMPILED ::= .build/rust-kompiled +RUST_TIMESTAMP ::= $(RUST_KOMPILED)/timestamp +SYNTAX_INPUT_DIR ::= tests/syntax +SYNTAX_OUTPUTS_DIR ::= .build/syntax-output + +SYNTAX_INPUTS ::= $(wildcard $(SYNTAX_INPUT_DIR)/*.rs) +SYNTAX_OUTPUTS ::= $(patsubst $(SYNTAX_INPUT_DIR)/%.rs,$(SYNTAX_OUTPUTS_DIR)/%.rs-parsed,$(SYNTAX_INPUTS)) + +.PHONY: clean build test syntax-test + +clean: + rm -r .build + +build: $(RUST_TIMESTAMP) + +test: syntax-test + +syntax-test: $(SYNTAX_OUTPUTS) + +$(RUST_TIMESTAMP): $(SEMANTICS_FILES) + $$(which kompile) rust-semantics/rust.md -o $(RUST_KOMPILED) + +$(SYNTAX_OUTPUTS_DIR)/%.rs-parsed: $(SYNTAX_INPUT_DIR)/%.rs $(RUST_TIMESTAMP) + mkdir -p $(SYNTAX_OUTPUTS_DIR) + kast --definition $(RUST_KOMPILED) $< --sort Crate > $@ diff --git a/rust-semantics/rust.md b/rust-semantics/rust.md new file mode 100644 index 0000000..e571dac --- /dev/null +++ b/rust-semantics/rust.md @@ -0,0 +1,8 @@ +```k +requires "syntax.md" + +module RUST + imports RUST-SHARED-SYNTAX +endmodule + +``` \ No newline at end of file diff --git a/rust-semantics/syntax.md b/rust-semantics/syntax.md new file mode 100644 index 0000000..58c41f3 --- /dev/null +++ b/rust-semantics/syntax.md @@ -0,0 +1,1040 @@ +Partial Rust Syntax +=================== + +Macros create ambiguities, so this syntax would be cleaner if it could rely on +macros being already expanded. However, expanding macros on MultiversX code +would force us to support more Rust features in the main semantics. + +```k +module RUST-SYNTAX + imports RUST-SHARED-SYNTAX + + syntax Underscore ::= "_" [token] + +``` + + https://doc.rust-lang.org/reference/identifiers.html + +```k + + // TODO: Not implemented properly + syntax Identifier ::= r"[A-Za-z_][A-Za-z0-9\\_]*" [prec(2), token] +endmodule + +module RUST-SHARED-SYNTAX + imports STRING-SYNTAX +``` + + https://doc.rust-lang.org/reference/crates-and-source-files.html + +```k + syntax Crate ::= InnerAttributes Items [symbol(crate)] + syntax InnerAttributes ::= List{InnerAttribute, ""} [symbol(innerAttributes)] + syntax Items ::= List{Item, ""} [symbol(items)] + +``` + + https://doc.rust-lang.org/reference/attributes.html + +```k + + syntax InnerAttribute ::= "#![" Attr "]" [symbol(innerAttribute)] + syntax OuterAttribute ::= "#[" Attr "]" [symbol(outerAttribute)] + + syntax Attr ::= Identifier + | Identifier "(" Identifier ")" + | Identifier "(" StringLiteral ")" + | Identifier "::" Identifier + +``` + + https://doc.rust-lang.org/reference/items.html + +```k + + syntax Item ::= OuterAttributes VisOrMacroItem [symbol(item)] + syntax NonEmptyOuterAttributes ::= OuterAttribute | OuterAttribute NonEmptyOuterAttributes + syntax OuterAttributes ::= "" | NonEmptyOuterAttributes + syntax VisOrMacroItem ::= VisItem | MacroItem + syntax MacroItem ::= MacroInvocationSemi | MacroRulesDefinition + syntax VisItem ::= MaybeVisibility VisItemItem + syntax MaybeVisibility ::= "" | Visibility + syntax VisItemItem ::= Module + | ExternCrate + | UseDeclaration + | Function + | TypeAlias + | Struct + | Enumeration + | Union + | ConstantItem + | StaticItem + | Trait + | Implementation + | ExternBlock + +``` + + https://doc.rust-lang.org/reference/visibility-and-privacy.html + +```k + + syntax Visibility ::= "pub" + | "pub" "(" "crate" ")" + | "pub" "(" "self" ")" + | "pub" "(" "super" ")" + | "pub" "(" "in" SimplePath ")" + +``` + https://doc.rust-lang.org/reference/paths.html#simple-paths +```k + + syntax SimplePath ::= SimplePathList | "::" SimplePathList + syntax SimplePathList ::= SimplePathSegment + syntax SimplePathList ::= SimplePathSegment "::" SimplePathList + syntax SimplePathSegment ::= Identifier | "super" | "self" | "crate" | "$crate" + +``` + https://doc.rust-lang.org/reference/items/modules.html +```k + + syntax Module ::= MaybeUnsafe "mod" Identifier ";" + | MaybeUnsafe "mod" Identifier "{" InnerAttributes Items "}" + +``` +https://doc.rust-lang.org/reference/items/extern-crates.html +```k + + syntax ExternCrate ::= "TODO: not needed yet, not implementing" + +``` + https://doc.rust-lang.org/reference/items/use-declarations.html +```k + + syntax UseDeclaration ::= "use" UseTree ";" + syntax UseTree ::= "*" + | MaybeSimplePathWithColon "*" + | "{" MaybeUseTreesMaybeComma "}" + | MaybeSimplePathWithColon "{" MaybeUseTreesMaybeComma "}" + | SimplePath + | SimplePath "as" SimplePathAs + syntax MaybeSimplePathWithColon ::= "::" | SimplePath "::" + syntax MaybeUseTreesMaybeComma ::= "" | UseTrees | UseTrees "," + syntax UseTrees ::= UseTree + syntax UseTrees ::= UseTree "," UseTrees + syntax MaybeSimplePathAs ::= "" | "as" SimplePathAs + syntax SimplePathAs ::= Identifier | Underscore + +``` + + https://doc.rust-lang.org/reference/items/functions.html + +```k + + syntax Function ::= FunctionQualifiers FunctionWithoutQualifiers + | FunctionWithoutQualifiers + syntax FunctionWithoutQualifiers ::= FunctionWithWhere + BlockExpressionOrSemicolon + syntax FunctionWithParams ::= "fn" IdentifierMaybeWithParams + "(" ")" + | "fn" IdentifierMaybeWithParams + "(" FunctionParameters ")" + syntax FunctionWithReturnType ::= FunctionWithParams + | FunctionWithParams FunctionReturnType + syntax FunctionWithWhere ::= FunctionWithReturnType | FunctionWithReturnType WhereClause + syntax IdentifierMaybeWithParams ::= Identifier | Identifier GenericParams + syntax BlockExpressionOrSemicolon ::= BlockExpression | ";" + + syntax FunctionQualifiers ::= "TODO: not needed yet, not implementing" + + syntax FunctionParameters ::= SelfParam MaybeComma + | MaybeSelfParamWithComma FunctionParameterList MaybeComma + syntax MaybeComma ::= "" | "," + syntax MaybeSelfParamWithComma ::= "" | SelfParam "," + syntax FunctionParameterList ::= FunctionParam + syntax FunctionParameterList ::= FunctionParam "," FunctionParameterList + + syntax SelfParam ::= OuterAttributes ShorthandOrTypedSelf + syntax ShorthandOrTypedSelf ::= ShorthandSelf | TypedSelf + syntax ShorthandSelf ::= MaybeReferenceOrReferenceLifetime MaybeMutable "self" + syntax MaybeReferenceOrReferenceLifetime ::= "" | ReferenceOrReferenceLifetime + syntax ReferenceOrReferenceLifetime ::= "&" | "&" Lifetime + syntax MaybeMutable ::= "" | "mut" + syntax TypedSelf ::= MaybeMutable "self" ":" Type + syntax FunctionParam ::= OuterAttributes FunctionParamDetail + // TODO: Missing cases + syntax FunctionParamDetail ::= FunctionParamPattern + syntax FunctionParamPattern ::= PatternNoTopAlt ":" TypeOrDots + // TODO: Missing cases + syntax TypeOrDots ::= Type + syntax FunctionReturnType ::= "->" Type + +``` + https://doc.rust-lang.org/reference/items/type-aliases.html + +```k + + syntax TypeAlias ::= "TODO: not needed yet, not implementing" + +``` + https://doc.rust-lang.org/reference/items/structs.html + +```k + + syntax Struct ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/items/enumerations.html + +```k + + syntax Enumeration ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/items/unions.html + +```k + + syntax Union ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/items/constant-items.html + +```k + + syntax ConstantItem ::= "const" IdentifierOrUnderscore ":" Type MaybeEqualsExpression ";" + syntax IdentifierOrUnderscore ::= Identifier | Underscore + syntax MaybeEqualsExpression ::= "" | "=" Expression + +``` + + https://doc.rust-lang.org/reference/items/static-items.html + +```k + + syntax StaticItem ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/items/traits.html + +```k + + syntax Trait ::= MaybeUnsafe "trait" Identifier MaybeGenericParams + MaybeColonMaybeTypeParamBounds MaybeWhereClause "{" + InnerAttributes AssociatedItems + "}" + syntax MaybeGenericParams ::= "" | GenericParams + syntax MaybeWhereClause ::= "" | WhereClause + syntax MaybeUnsafe ::= "" | "unsafe" + syntax MaybeColonMaybeTypeParamBounds ::= "" | ":" MaybeTypeParamBounds + syntax MaybeTypeParamBounds ::= "" | TypeParamBounds + syntax AssociatedItems ::= List{AssociatedItem, ""} + +``` + + https://doc.rust-lang.org/reference/items/implementations.html + +```k + + syntax Implementation ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/items/external-blocks.html + +```k + + syntax ExternBlock ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/literal-expr.html + +```k + + syntax LiteralExpression ::= CharLiteral | StringLiteral | RawStringLiteral + | ByteLiteral | ByteStringLiteral | RawByteStringLiteral + | CStringLiteral | RawCStringLiteral + | IntegerLiteral | FloatLiteral + | "true" | "false" + + syntax CharLiteral ::= "TODO: not needed yet, not implementing" + // TODO: Not implemented properly + syntax StringLiteral ::= String + syntax RawStringLiteral ::= "TODO: not needed yet, not implementing" + syntax ByteLiteral ::= "TODO: not needed yet, not implementing" + syntax ByteStringLiteral ::= "TODO: not needed yet, not implementing" + syntax RawByteStringLiteral ::= "TODO: not needed yet, not implementing" + syntax CStringLiteral ::= "TODO: not needed yet, not implementing" + syntax RawCStringLiteral ::= "TODO: not needed yet, not implementing" + // TODO: Not implemented properly + syntax IntegerLiteral ::= r"[0-9]([0-9]|_)*([a-zA-Z][a-zA-Z0-9_]*)?" [token] + syntax FloatLiteral ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/trait-bounds.html + +```k + + syntax TypeParamBounds ::= "TODO: not needed yet, not implementing" + syntax Lifetime ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/items/generics.html#where-clauses + +```k + + syntax WhereClause ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions.html + + https://doc.rust-lang.org/reference/expressions/operator-expr.html + https://doc.rust-lang.org/reference/expressions/operator-expr.html#negation-operators + https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators + https://doc.rust-lang.org/reference/expressions/operator-expr.html#comparison-operators + https://doc.rust-lang.org/reference/expressions/operator-expr.html#lazy-boolean-operators + https://doc.rust-lang.org/reference/expressions/operator-expr.html#borrow-operators + https://doc.rust-lang.org/reference/expressions/operator-expr.html#assignment-expressions + https://doc.rust-lang.org/reference/expressions/operator-expr.html#compound-assignment-expressions + https://doc.rust-lang.org/reference/expressions/array-expr.html#array-and-slice-indexing-expressions + https://doc.rust-lang.org/reference/expressions/field-expr.html + https://doc.rust-lang.org/reference/expressions/range-expr.html + +```k + // Flattened in order to handle precedence. + syntax Expression ::= NonEmptyOuterAttributes Expression + | ExpressionWithBlock + + syntax Expression ::= LiteralExpression + | GroupedExpression + | ArrayExpression + | AwaitExpression + | TupleExpression + | TupleIndexingExpression + | StructExpression + | ClosureExpression + | AsyncBlockExpression + | ContinueExpression + | BreakExpression + | ReturnExpression + | UnderscoreExpression + + | CallExpression + | ErrorPropagationExpression + | TypeCastExpression + // TODO: Removed because it causes ambiguities. + // | MacroInvocation + + // Several sub-expressions were included directly in Expression + // to make it easy to disambiguate based on priority + syntax Expression ::= PathExpression + + > MethodCallExpression + + > Expression "." Identifier // FieldExpression + + // > CallExpression + | Expression "[" Expression "]" + + // > ErrorPropagationExpression + + > "&" Expression + | "&&" Expression + | "&" "mut" Expression + | "&&" "mut" Expression + | "-" Expression + | "!" Expression + | DereferenceExpression + + // > left: + // TypeCastExpression + + > left: + Expression "*" Expression + | Expression "/" Expression + | Expression "%" Expression + + > left: + Expression "+" Expression + | Expression "-" Expression + + > left: + Expression "<<" Expression + | Expression ">>" Expression + + > left: + Expression "&" Expression + | Expression "^" Expression + | Expression "|" Expression + + > Expression "==" Expression + | Expression "!=" Expression + | Expression ">" Expression + | Expression "<" Expression + | Expression ">=" Expression + | Expression "<=" Expression + + > left: + Expression "&&" Expression + | Expression "||" Expression + + > Expression ".." Expression + + > right: + Expression "=" Expression + | Expression "+=" Expression + | Expression "-=" Expression + | Expression "*=" Expression + | Expression "/=" Expression + | Expression "%=" Expression + | Expression "&=" Expression + | Expression "|=" Expression + | Expression "^=" Expression + | Expression "<<=" Expression + | Expression ">>=" Expression + + syntax ExpressionWithBlock ::= BlockExpression + | UnsafeBlockExpression + | LoopExpression + | IfExpression + | IfLetExpression + | MatchExpression + +``` + + https://doc.rust-lang.org/reference/expressions/path-expr.html + +```k + + syntax PathExpression ::= PathInExpression | QualifiedPathInExpression + +``` + + https://doc.rust-lang.org/reference/paths.html#paths-in-expressions + +```k + + syntax PathInExpression ::= PathExprSegments | "::" PathExprSegments + syntax PathExprSegments ::= PathExprSegment + | PathExprSegment "::" PathExprSegments + syntax PathExprSegment ::= PathIdentSegment | PathIdentSegment "::" GenericArgs + syntax PathIdentSegment ::= Identifier | "super" | "self" | "Self" | "crate" | "$crate" + syntax GenericArgs ::= "<" ">" | "<" GenericArgList MaybeComma ">" + syntax GenericArgList ::= GenericArg | GenericArg "," GenericArgList + // TODO: Not implemented properly + syntax GenericArg ::= Type + +``` + + https://doc.rust-lang.org/reference/paths.html#qualified-paths + +```k + + syntax QualifiedPathInExpression ::= "TODO: not needed yet, not implementing" + syntax QualifiedPathInType ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-dereference-operator + +```k + + syntax DereferenceExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator + +```k + + syntax ErrorPropagationExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/operator-expr.html#type-cast-expressions + +```k + + syntax TypeCastExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/grouped-expr.html + +```k + + syntax GroupedExpression ::= "(" Expression ")" + +``` + + https://doc.rust-lang.org/reference/expressions/array-expr.html + +```k + + syntax ArrayExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/await-expr.html + +```k + + syntax AwaitExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/tuple-expr.html + +```k + + syntax TupleExpression ::= "(" MaybeTupleElements ")" + syntax MaybeTupleElements ::= "" | TupleElements + syntax TupleElements ::= Expression "," + | Expression "," Expression + | Expression "," TupleElements + +``` + + https://doc.rust-lang.org/reference/expressions/tuple-expr.html#tuple-indexing-expressions + +```k + + syntax TupleIndexingExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/struct-expr.html + +```k + + syntax StructExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/call-expr.html + +```k + + // TODO: Not implemented properly to avoid ambiguities + syntax CallExpression ::= PathExpression "(" MaybeCallParams ")" + syntax MaybeCallParams ::= "" | CallParams + syntax CallParams ::= CallParamsList | CallParamsList "," + syntax CallParamsList ::= Expression | Expression "," CallParams + +``` + + https://doc.rust-lang.org/reference/expressions/method-call-expr.html + +```k + + // TODO: Not implemented properly to avoid ambiguities + syntax MethodCallExpression ::= PathExpression "." PathExprSegment "(" MaybeCallParams ")" + | MethodCallExpression "." PathExprSegment "(" MaybeCallParams ")" + +``` + + https://doc.rust-lang.org/reference/expressions/closure-expr.html + +```k + + syntax ClosureExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/block-expr.html#async-blocks + +```k + + syntax AsyncBlockExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/loop-expr.html#continue-expressions + +```k + + syntax ContinueExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/loop-expr.html#break-expressions + +```k + + syntax BreakExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/return-expr.html + +```k + + syntax ReturnExpression ::= "return" MaybeExpression + syntax MaybeExpression ::= "" | Expression + +``` + + https://doc.rust-lang.org/reference/expressions/underscore-expr.html + +```k + + syntax UnderscoreExpression ::= Underscore + +``` + + https://doc.rust-lang.org/reference/macros.html#macro-invocation + +```k + + syntax MacroInvocation ::= SimplePath "!" DelimTokenTree + // TODO: Not implemented properly + syntax DelimTokenTree ::= "(" MaybeCallParams ")" + // TODO: Not implemented properly + syntax MacroInvocationSemi ::= MacroInvocation ";" + +``` + + https://doc.rust-lang.org/reference/macros-by-example.html + +```k + + syntax MacroRulesDefinition ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/block-expr.html + +```k + + syntax BlockExpression ::= "{" InnerAttributes MaybeStatements "}" + syntax MaybeStatements ::= "" | Statements + syntax Statements ::= NonEmptyStatements + // TODO: Not implemented properly + | NonEmptyStatements Expression + // TODO: Not implemented properly + | Expression + syntax NonEmptyStatements ::= Statement | Statement NonEmptyStatements + +``` + + https://doc.rust-lang.org/reference/expressions/block-expr.html#unsafe-blocks + +```k + + syntax UnsafeBlockExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/loop-expr.html + +```k + + syntax LoopExpression ::= LoopExpressionDetail | LoopLabel LoopExpressionDetail + syntax LoopExpressionDetail ::= InfiniteLoopExpression + | PredicateLoopExpression + | PredicatePatternLoopExpression + | IteratorLoopExpression + | LabelBlockExpression + +``` + + https://doc.rust-lang.org/reference/expressions/loop-expr.html#loop-labels + +```k + + syntax LoopLabel ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/loop-expr.html#infinite-loops + +```k + + syntax InfiniteLoopExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-loops + +```k + + syntax PredicateLoopExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-pattern-loops + +```k + + syntax PredicatePatternLoopExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/loop-expr.html#iterator-loops + +```k + + syntax IteratorLoopExpression ::= "for" Pattern "in" ExpressionExceptStructExpression BlockExpression + +``` + + https://doc.rust-lang.org/reference/expressions/loop-expr.html#labelled-block-expressions + +```k + + syntax LabelBlockExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/if-expr.html#if-expressions + +```k + + syntax IfExpression ::= "if" ExpressionExceptStructExpression BlockExpression MaybeIfElseExpression + syntax MaybeIfElseExpression ::= "" | "else" IfElseExpression + syntax IfElseExpression ::= BlockExpression | IfExpression | IfLetExpression + // TODO: Not implemented properly + syntax ExpressionExceptStructExpression ::= Expression + +``` + + https://doc.rust-lang.org/reference/expressions/if-expr.html#if-let-expressions + +```k + + syntax IfLetExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/expressions/match-expr.html + +```k + + syntax MatchExpression ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/statements.html + +```k + + syntax Statement ::= ";" + // TODO: Item creates ambiguities, should be resolved by + // expanding macros before parsing. + // | Item + | LetStatement + | ExpressionStatement + | MacroInvocationSemi + +``` + + https://doc.rust-lang.org/reference/statements.html#let-statements + +```k + + syntax LetStatement ::= OuterAttributes "let" PatternNoTopAlt MaybeColonType MaybeEqualsExpressionMaybeElseBlockExpression ";" + syntax MaybeColonType ::= "" | ":" Type + syntax MaybeEqualsExpressionMaybeElseBlockExpression ::= "" | "=" Expression MaybeElseBlockExpression + // TODO: Not implemented properly to remove ambiguities + syntax MaybeElseBlockExpression ::= "" // | "else" BlockExpression + +``` + + https://doc.rust-lang.org/reference/statements.html#expression-statements + +```k + + // TODO: Not implemented properly + syntax ExpressionStatement ::= Expression ";" + syntax MaybeSemicolon ::= "" | ";" + +``` + + https://doc.rust-lang.org/reference/patterns.html + +```k + + syntax Pattern ::= PatternNoTopAlts | "|" PatternNoTopAlts + syntax PatternNoTopAlts ::= PatternNoTopAlt + syntax PatternNoTopAlts ::= PatternNoTopAlt "|" PatternNoTopAlts + syntax PatternNoTopAlt ::= PatternWithoutRange + | RangePattern + syntax PatternWithoutRange ::= LiteralPattern + | IdentifierPattern + | WildcardPattern + | RestPattern + | ReferencePattern + | StructPattern + | TupleStructPattern + | TuplePattern + | GroupedPattern + | SlicePattern + | PathPattern + | MacroInvocation + +``` + + https://doc.rust-lang.org/reference/patterns.html#range-patterns + +```k + + syntax RangePattern ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/patterns.html#literal-patterns + +```k + + syntax LiteralPattern ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/patterns.html#identifier-patterns + +```k + + syntax IdentifierPattern ::= IdentifierPattern1 | "ref" IdentifierPattern1 + syntax IdentifierPattern1 ::= IdentifierPattern2 | "mut" IdentifierPattern2 + syntax IdentifierPattern2 ::= Identifier | Identifier "@" PatternNoTopAlt + +``` + + https://doc.rust-lang.org/reference/patterns.html#wildcard-pattern + +```k + + syntax WildcardPattern ::= Underscore + +``` + + https://doc.rust-lang.org/reference/patterns.html#rest-patterns + +```k + + syntax RestPattern ::= ".." + +``` + + https://doc.rust-lang.org/reference/patterns.html#reference-patterns + +```k + + syntax ReferencePattern ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/patterns.html#struct-patterns + +```k + + syntax StructPattern ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/patterns.html#tuple-struct-patterns + +```k + + syntax TupleStructPattern ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/patterns.html#tuple-patterns + +```k + + syntax TuplePattern ::= "(" MaybeTuplePatternItems ")" + syntax MaybeTuplePatternItems ::= "" | TuplePatternItems + syntax TuplePatternItems ::= Pattern "," | RestPattern | Patterns | Patterns "," + syntax Patterns ::= Pattern | Pattern "," Patterns + +``` + + https://doc.rust-lang.org/reference/patterns.html#grouped-patterns + +```k + + syntax GroupedPattern ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/patterns.html#slice-patterns + +```k + + syntax SlicePattern ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/patterns.html#path-patterns + +```k + + syntax PathPattern ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/items/associated-items.html + +```k + + syntax AssociatedItem ::= OuterAttributes AssociatedItemDetails + syntax AssociatedItemDetails ::= MacroInvocationSemi | MaybeVisibility AssociatedItemDetailsDetails + syntax AssociatedItemDetailsDetails ::= TypeAlias | ConstantItem | Function + +``` + + https://doc.rust-lang.org/reference/types.html#type-expressions + +```k + + syntax Type ::= TypeNoBounds + | ImplTraitType + | TraitObjectType + syntax TypeNoBounds ::= ParenthesizedType + | ImplTraitTypeOneBound + | TraitObjectTypeOneBound + | TypePath + | TupleType + | NeverType + | RawPointerType + | ReferenceType + | ArrayType + | SliceType + | InferredType + | QualifiedPathInType + | BareFunctionType + | MacroInvocation +``` + + https://doc.rust-lang.org/reference/types/impl-trait.html + +```k + + syntax ImplTraitType ::= "TODO: not needed yet, not implementing" + syntax ImplTraitTypeOneBound ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/types/trait-object.html + +```k + + syntax TraitObjectType ::= "TODO: not needed yet, not implementing" + syntax TraitObjectTypeOneBound ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/types.html#parenthesized-types + +```k + + syntax ParenthesizedType ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/paths.html#paths-in-types + +```k + + syntax TypePath ::= TypePathSegments | "::" TypePathSegments + syntax TypePathSegments ::= TypePathSegment | TypePathSegment "::" TypePathSegments + syntax TypePathSegment ::= PathIdentSegment | PathIdentSegment TypePathSegmentSuffix + syntax TypePathSegmentSuffix ::= TypePathSegmentSuffixSuffix | "::" TypePathSegmentSuffixSuffix + // TODO: Not implemented properly + syntax TypePathSegmentSuffixSuffix ::= GenericArgs + +``` + + https://doc.rust-lang.org/reference/types/tuple.html#tuple-types + +```k + + syntax TupleType ::= "(" ")" + | "(" NonEmptyTypeCsv MaybeComma ")" + syntax NonEmptyTypeCsv ::= Type | Type "," NonEmptyTypeCsv + +``` + + https://doc.rust-lang.org/reference/types/never.html + +```k + + syntax NeverType ::= "!" + +``` + + https://doc.rust-lang.org/reference/types/pointer.html#raw-pointers-const-and-mut + +```k + + syntax RawPointerType ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/types/array.html + +```k + + syntax ArrayType ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/types/slice.html + +```k + + syntax SliceType ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/types/inferred.html + +```k + + syntax InferredType ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/types/function-pointer.html + +```k + + syntax BareFunctionType ::= "TODO: not needed yet, not implementing" + +``` + + https://doc.rust-lang.org/reference/types/pointer.html#references--and-mut + +```k + + syntax ReferenceType ::= "&" MaybeLifetime MaybeMutable TypeNoBounds + syntax MaybeLifetime ::= "" | Lifetime + +``` + + https://doc.rust-lang.org/reference/items/generics.html + +```k + + syntax GenericParams ::= "<" ">" + | "<" GenericParamList MaybeComma ">" + syntax GenericParamList ::= GenericParam | GenericParam "," GenericParamList + // TODO: Not implemented properly + syntax GenericParam ::= TypeParam + // TODO: Not implemented properly + syntax TypeParam ::= Identifier + + syntax Underscore [token] + syntax Identifier [token] +endmodule +``` diff --git a/tests/syntax/erc_20_token.rs b/tests/syntax/erc_20_token.rs new file mode 100644 index 0000000..8e03d57 --- /dev/null +++ b/tests/syntax/erc_20_token.rs @@ -0,0 +1,150 @@ +// This contract is a translation of +// https://github.com/Pi-Squared-Inc/pi2-examples/blob/b63d0a78922874a486be8a0395a627425fb5a052/solidity/src/tokens/SomeToken.sol +// +// The main change is that the contract does not issue specific error objects +// (e.g. ERC20InsufficientBalance), it just calls `require!` with various +// (string) explanations. +// +// Also, the `totalSupply` endpoint is declared implicitely as a view for +// `s_total_supply`. + +#![no_std] + +#[allow(unused_imports)] +use multiversx_sc::imports::*; + +#[multiversx_sc::contract] +pub trait Erc20Token { + #[view(totalSupply)] + #[storage_mapper("total_supply")] + fn s_total_supply(&self) -> SingleValueMapper; + + #[view(getName)] + #[storage_mapper("name")] + fn s_name(&self) -> SingleValueMapper; + + #[view(getSymbol)] + #[storage_mapper("symbol")] + fn s_symbol(&self) -> SingleValueMapper; + + #[view(getBalances)] + #[storage_mapper("balances")] + fn s_balances(&self, address: &ManagedAddress) -> SingleValueMapper; + + #[view(getAllowances)] + #[storage_mapper("allowances")] + fn s_allowances(&self, account: &ManagedAddress, spender: &ManagedAddress) -> SingleValueMapper; + + #[event("Transfer")] + fn transfer_event(&self, #[indexed] from: &ManagedAddress, #[indexed] to: &ManagedAddress, value: &BigUint); + #[event("Approval")] + fn approval_event(&self, #[indexed] owner: &ManagedAddress, #[indexed] spender: &ManagedAddress, value: &BigUint); + + + #[init] + fn init(&self, name: &ManagedBuffer, symbol: &ManagedBuffer, init_supply: &BigUint) { + self.s_name().set_if_empty(name); + self.s_symbol().set_if_empty(symbol); + self._mint(&self.blockchain().get_caller(), init_supply); + } + + #[upgrade] + fn upgrade(&self) {} + + #[view(decimals)] + fn decimals(&self) -> u8 { + return 18; + } + + // Already declared above + // #[view(totalSupply)] + // fn total_supply(&self) -> BigUint { + // return self.s_total_supply().get(); + // } + + #[view(name)] + fn name(&self) -> ManagedBuffer { + return self.s_name().get(); + } + + #[view(symbol)] + fn symbol(&self) -> ManagedBuffer { + return self.s_symbol().get(); + } + + #[view(balanceOf)] + fn balance_of(&self, account: &ManagedAddress) -> BigUint { + self.s_balances(account).get() + } + + #[endpoint(transfer)] + fn transfer(&self, to: &ManagedAddress, value: BigUint) -> bool { + let owner = self.blockchain().get_caller(); + self._transfer(&owner, to, &value); + true + } + + #[view(allowance)] + fn allowance(&self, owner: &ManagedAddress, spender: &ManagedAddress) -> BigUint { + self.s_allowances(owner, spender).get() + } + + #[endpoint(approve)] + fn approve(&self, spender: &ManagedAddress, value: &BigUint) -> bool { + let owner = self.blockchain().get_caller(); + self._approve(&owner, spender, value, true); + true + } + + #[endpoint(transferFrom)] + fn transfer_from(&self, from: &ManagedAddress, to: &ManagedAddress, value: &BigUint) -> bool { + let spender = self.blockchain().get_caller(); + self._spend_allowance(from, &spender, value); + self._transfer(from, to, value); + return true; + } + + fn _transfer(&self, from: &ManagedAddress, to: &ManagedAddress, value: &BigUint) { + require!(!from.is_zero(), "Invalid sender"); + require!(!to.is_zero(), "Invalid receiver"); + self._update(from, to, value); + self.transfer_event(from, to, value); + } + + fn _update(&self, from: &ManagedAddress, to: &ManagedAddress, value: &BigUint) { + if from.is_zero() { + self.s_total_supply().set(self.s_total_supply().get() + value); + } else { + let from_balance = self.s_balances(from).get(); + require!(value <= &from_balance, "Insufficient balance"); + self.s_balances(from).set(self.s_balances(from).get() - value); + }; + + if to.is_zero() { + self.s_total_supply().set(self.s_total_supply().get() - value); + } else { + self.s_balances(to).set(self.s_balances(to).get() + value); + } + } + + fn _mint(&self, account: &ManagedAddress, value: &BigUint) { + require!(!account.is_zero(), "Zero address"); + self._update(&ManagedAddress::zero(), account, value); + } + + fn _approve(&self, owner: &ManagedAddress, spender: &ManagedAddress, value: &BigUint, emit_event: bool) { + require!(!owner.is_zero(), "Invalid approver"); + require!(!spender.is_zero(), "Invalid spender"); + self.s_allowances(owner, spender).set(value); + if emit_event { + self.approval_event(owner, spender, value); + } + } + + fn _spend_allowance(&self, owner: &ManagedAddress, spender: &ManagedAddress, value: &BigUint) { + let current_allowance = self.allowance(owner, spender); + require!(value <= ¤t_allowance, "Insuficient allowance"); + self._approve(owner, spender, &(current_allowance - value), false); + } + +} diff --git a/tests/syntax/lending.rs b/tests/syntax/lending.rs new file mode 100644 index 0000000..23353c8 --- /dev/null +++ b/tests/syntax/lending.rs @@ -0,0 +1,409 @@ +// This contract is a translation of +// https://github.com/Pi-Squared-Inc/pi2-examples/blob/94e77f575041c10df678bac0f693815ec19e126b/solidity/src/lending/LendingPool.sol +// +// Changes: +// * Does not emit events +// * Does not issue specific error objects, (e.g. TransferFailed), it just +// calls `require!` with various (string) explanations. +#![no_std] + +#[allow(unused_imports)] +use multiversx_sc::imports::*; + +pub const USD_PER_TOKEN: u64 = 10_u64; +pub const INTEREST_RATE: u64 = 10_000_u64; +pub const RESERVE_RATIO: u64 = 10_000_u64; +pub const BPS: u64 = 100_000_u64; + +pub const LIQUIDATION_THRESHOLD: u64 = 80_000_u64; +pub const LIQUIDATION_REWARD: u64 = 1_000_u64; +pub const MIN_HEALTH_FACTOR: u64 = 1_000_000_000_000_000_000_u64; +pub const PRECISION: u64 = 1_000_000_u64; +pub const BLOCKS_PER_YEAR: u64 = 365_u64 * 24_u64 * 60_u64 * 10_u64; + +#[multiversx_sc::contract] +pub trait Lending { + #[init] + fn init(&self) { + } + + #[upgrade] + fn upgrade(&self) {} + + #[view(getTokens)] + #[storage_mapper("tokens")] + fn tokens(&self) -> UnorderedSetMapper; + + #[view(getTokenAmount)] + #[storage_mapper("token_amount")] + fn token_amount(&self, token: &TokenIdentifier) -> SingleValueMapper; + + #[view(getTokenBorrowed)] + #[storage_mapper("token_borrowed")] + fn token_borrowed(&self, token: &TokenIdentifier) -> SingleValueMapper; + + #[view(getTokenShares)] + #[storage_mapper("token_shares")] + fn token_shares(&self, token: &TokenIdentifier) -> SingleValueMapper; + + #[view(getTokenBorrowedShares)] + #[storage_mapper("token_borrowed_shares")] + fn token_borrowed_shares(&self, token: &TokenIdentifier) -> SingleValueMapper; + + #[view(getUserShares)] + #[storage_mapper("user_shares")] + fn user_shares(&self, user: &ManagedAddress, token: &TokenIdentifier) -> SingleValueMapper; + + #[view(getUserBorrowedShares)] + #[storage_mapper("user_borrowed_shares")] + fn user_borrowed_shares(&self, user: &ManagedAddress, token: &TokenIdentifier) -> SingleValueMapper; + + #[view(getLastTimestamp)] + #[storage_mapper("last_timestamp")] + fn last_timestamp(&self, token: &TokenIdentifier) -> SingleValueMapper; + + + #[endpoint(whitelist)] + fn whitelist(&self, token: TokenIdentifier) { + self.tokens().insert(token); + } + + #[payable("*")] + #[endpoint(supply)] + fn supply(&self) { + let caller = + self.blockchain().get_caller(); + let (token, amount) = + self.call_value().single_fungible_esdt(); + + require!(amount > BigUint::zero(), "Zero amount!"); + require!(self.tokens().contains(&token), "Token not whitelisted."); + + self.accrue_interest(&token); + + let token_amount = self.token_amount(&token).get(); + let token_shares = self.token_shares(&token).get(); + + let shares = + self.to_shares( + &amount, + &token_amount, + &token_shares, + false + ); + + self.token_amount(&token).set(token_amount + amount); + self.token_shares(&token).set(token_shares + &shares); + + let user_shares = self.user_shares(&caller, &token).get(); + + self.user_shares(&caller, &token).set(user_shares + shares); + } + + + #[endpoint(borrow)] + fn borrow(&self, token: &TokenIdentifier, amount: &BigUint) { + require!(self.tokens().contains(&token), "Token not whitelisted"); + require!(self.vault_above_reserve_ratio(token, amount), "Not enough tokens"); + + self.accrue_interest(token); + + let token_borrowed = self.token_borrowed(&token).get(); + let token_borrowed_shares = self.token_borrowed_shares(&token).get(); + + let shares = + self.to_shares( + &amount, + &token_borrowed, + &token_borrowed_shares, + false + ); + + self.token_borrowed(&token).set(token_borrowed + amount); + self.token_borrowed_shares(&token).set(&token_borrowed_shares + &shares); + + let caller = self.blockchain().get_caller(); + let user_borrowed_shares = self.user_borrowed_shares(&caller, &token).get(); + + self.user_borrowed_shares(&caller, &token).set(user_borrowed_shares + shares); + + self.send().direct_esdt(&caller, token, 0, amount); + + require!(self.health_factor(&caller) >= MIN_HEALTH_FACTOR, "Not healthy enough."); + } + + + #[payable("*")] + #[endpoint(repay)] + fn repay(&self) { + let (token, amount) = self.call_value().single_fungible_esdt(); + require!(self.tokens().contains(&token), "Token not whitelisted"); + + self.accrue_interest(&token); + let caller = self.blockchain().get_caller(); + let token_borrowed = self.token_borrowed(&token).get(); + let token_borrowed_shares = self.token_borrowed_shares(&token).get(); + let user_borrow_shares = self.user_borrowed_shares(&caller, &token).get(); + let shares_for_amount = + self.to_shares( + &amount, + &token_borrowed, + &token_borrowed_shares, + true + ); + + require!(shares_for_amount <= user_borrow_shares, "Too many shares"); + + self.token_borrowed(&token).set(token_borrowed - amount); + self.token_borrowed_shares(&token).set(token_borrowed_shares - &shares_for_amount); + self.user_borrowed_shares(&caller, &token).set(user_borrow_shares - &shares_for_amount); + } + + + #[endpoint(redeem)] + fn redeem(&self, token: &TokenIdentifier, shares: &BigUint) { + require!(self.tokens().contains(&token), "Token not whitelisted"); + + self.accrue_interest(token); + + require!(&BigUint::zero() < shares, "Zero shares"); + + let caller = self.blockchain().get_caller(); + let user_shares = self.user_shares(&caller, token).get(); + + require!(shares <= &user_shares, "Too many shares"); + + let token_amount = self.token_amount(token).get(); + let token_shares = self.token_shares(token).get(); + + let amount = + self.to_amount( + &shares, + &token_amount, + &token_shares, + false + ); + + require!(BigUint::zero() < amount, "Amount too low"); + require!(amount <= self.balance(token), "Not enough tokens"); + + self.token_amount(&token).set(token_amount - &amount); + self.token_shares(&token).set(token_shares - shares); + self.user_shares(&caller, &token).set(user_shares - shares); + + require!(self.health_factor(&caller) >= MIN_HEALTH_FACTOR, "Too ill"); + + self.send().direct_esdt(&caller, token, 0, &amount); + } + + + #[payable("*")] + #[endpoint(liquidate)] + fn liquidate( + &self, + borrower: &ManagedAddress, + collateral_token: &TokenIdentifier, + ) { + let caller = self.blockchain().get_caller(); + let (borrow_token, borrow_liquidation_amount) = + self.call_value().single_fungible_esdt(); + + self.accrue_interest(collateral_token); + self.accrue_interest(&borrow_token); + + require!(&caller != borrower, "Cannot self-liquidate"); + require!(self.tokens().contains(&borrow_token), "Borrow token not whitelisted"); + require!(self.tokens().contains(collateral_token), "Collateral token not whitelisted"); + + require!(self.health_factor(&borrower) < MIN_HEALTH_FACTOR, "Too healthy"); + + let collateral_shares = + self.user_shares(borrower, collateral_token).get(); + let borrow_shares = + self.user_borrowed_shares(borrower, &borrow_token).get(); + require!(collateral_shares > BigUint::zero(), "No collateral shares"); + require!(borrow_shares > BigUint::zero(), "No borrow shares"); + require!(borrow_liquidation_amount > BigUint::zero(), "No amount received"); + + let token_borrowed = self.token_borrowed(&borrow_token).get(); + let token_borrowed_shares = self.token_borrowed_shares(&borrow_token).get(); + + let borrow_amount = + self.to_amount( + &borrow_shares, + &token_borrowed, + &token_borrowed_shares, + true + ); + + require!(borrow_liquidation_amount <= borrow_amount, "Liquidating too much."); + + let token_amount = self.token_amount(collateral_token).get(); + let token_shares = self.token_shares(collateral_token).get(); + + let collateral_amount = + self.to_amount( + &collateral_shares, + &token_amount, + &token_shares, + false + ); + require!(collateral_amount > 0, "Collateral amount too low"); + + let collateral_to_liquidate = + self.to_usd(&borrow_token, &borrow_liquidation_amount) + / self.to_usd(&collateral_token, &BigUint::from(1_u64)); + let liquidation_reward = + (&collateral_to_liquidate * &BigUint::from(LIQUIDATION_REWARD)) / BigUint::from(BPS); + let total_collateral_to_liquidate = collateral_to_liquidate + liquidation_reward; + + require!(total_collateral_to_liquidate <= collateral_amount, "Not enough collateral"); + + let borrow_liquidation_shares = + self.to_shares( + &borrow_liquidation_amount, + &token_borrowed, + &token_borrowed_shares, + false + ); + + self.token_borrowed(&borrow_token).set(token_borrowed - &borrow_liquidation_amount); + self.token_borrowed_shares(&borrow_token).set(token_borrowed_shares - &borrow_liquidation_shares); + self.user_borrowed_shares(&borrower, &borrow_token).set(borrow_shares - &borrow_liquidation_shares); + + let total_collateral_liquidation_shares = + self.to_shares( + &total_collateral_to_liquidate, + &token_amount, + &token_shares, + false + ); + + require!(total_collateral_to_liquidate < self.token_amount(&collateral_token).get(), "Cannot repay collateral"); + self.token_amount(&collateral_token).set(token_amount - &total_collateral_to_liquidate); + self.token_shares(&collateral_token).set(token_shares - &total_collateral_liquidation_shares); + self.user_shares(&borrower, &collateral_token).set(collateral_shares - &total_collateral_liquidation_shares); + + self.send().direct_esdt(&caller, collateral_token, 0, &total_collateral_to_liquidate); + } + + + fn health_factor(&self, user: &ManagedAddress) -> BigUint { + let total_token_collateral = + self.get_user_total_token_collateral(user); + let total_borrow_value = + self.get_user_total_borrow_value(user); + + if total_borrow_value == 0 { + return BigUint::from(BigUint::from(100_u64) * BigUint::from(MIN_HEALTH_FACTOR)); + }; + let collateral_value_with_threshold = + (total_token_collateral * LIQUIDATION_THRESHOLD) / BPS; + (collateral_value_with_threshold * MIN_HEALTH_FACTOR) / total_borrow_value + } + + + fn vault_above_reserve_ratio( + &self, + token: &TokenIdentifier, + borrowed_amount: &BigUint + ) -> bool { + let total_amount = self.token_amount(token).get(); + let min_reserve = (&total_amount * RESERVE_RATIO) / BPS; + let balance = self.balance(token); + &total_amount > &BigUint::zero() && + balance >= min_reserve + borrowed_amount + } + + + fn balance(&self, token: &TokenIdentifier) -> BigUint { + self.blockchain().get_sc_balance(&EgldOrEsdtTokenIdentifier::esdt(token.clone()), 0) + } + + + fn get_user_total_borrow_value(&self, user: &ManagedAddress) -> BigUint { + let mut total_usd = BigUint::zero(); + for token in self.tokens().into_iter() { + let shares = self.user_borrowed_shares(user, &token).get(); + let amount = + self.to_amount( + &shares, + &self.token_borrowed(&token).get(), + &self.token_borrowed_shares(&token).get(), + false + ); + total_usd += self.to_usd(&token, &amount); + }; + total_usd + } + + + fn get_user_total_token_collateral(&self, user: &ManagedAddress) -> BigUint { + let mut total_usd = BigUint::zero(); + for token in self.tokens().into_iter() { + let shares = self.user_shares(user, &token).get(); + let amount = + self.to_amount( + &shares, + &self.token_amount(&token).get(), + &self.token_shares(&token).get(), + false + ); + total_usd += self.to_usd(&token, &amount); + }; + total_usd + } + + + fn accrue_interest(&self, token: &TokenIdentifier) { + let last_timestamp = self.last_timestamp(token).get(); + let current_timestamp = self.blockchain().get_block_timestamp(); + require!(last_timestamp <= current_timestamp, "Broken timestamps."); + if last_timestamp == current_timestamp { + return; + }; + + let delta_time = current_timestamp - last_timestamp; + + let borrowed_start = self.token_borrowed(token).get(); + + // Calculate interest accrued + let interest_earned = + (BigUint::from(delta_time) * + &borrowed_start * + INTEREST_RATE) / + (BPS * BLOCKS_PER_YEAR); + + self.token_borrowed(&token).set(borrowed_start + &interest_earned); + self.token_amount(&token).set(self.token_amount(&token).get() + &interest_earned); + } + + fn to_usd(&self, _token: &TokenIdentifier, amount: &BigUint) -> BigUint { + amount * &BigUint::from(USD_PER_TOKEN) + } + + fn to_shares(&self, amount: &BigUint, total_amount: &BigUint, total_shares: &BigUint, round_up: bool) -> BigUint { + if total_amount == &BigUint::zero() { + amount.clone() + } else { + self.div_round(&(amount * total_shares), &total_amount, round_up) + } + } + + fn to_amount(&self, shares: &BigUint, total_amount: &BigUint, total_shares: &BigUint, round_up: bool) -> BigUint { + if total_shares == &BigUint::zero() { + shares.clone() + } else { + self.div_round(&(shares * total_amount), &total_shares, round_up) + } + } + + fn div_round(&self, a: &BigUint, b: &BigUint, round_up: bool) -> BigUint { + if round_up { + (a + b - BigUint::from(1_u64)) / b + } else { + a / b + } + } + +} diff --git a/tests/syntax/staking.rs b/tests/syntax/staking.rs new file mode 100644 index 0000000..ad9cb42 --- /dev/null +++ b/tests/syntax/staking.rs @@ -0,0 +1,93 @@ +// This contract is a translation of +// https://github.com/Pi-Squared-Inc/pi2-examples/blob/06402f45f37887006544c8be2c1c4d2aa6fdea7a/solidity/src/staking/LiquidStaking.sol + +#![no_std] + +#[allow(unused_imports)] +use multiversx_sc::imports::*; + +pub const YEARLY_INTEREST: u64 = 7_000; +pub const BPS: u64 = 100_000; +pub const SECONDS_IN_DAY: u64 = 24 * 60 * 60; +pub const SECONDS_IN_YEAR: u64 = 365 * SECONDS_IN_DAY; + +#[multiversx_sc::contract] +pub trait Staking { + #[view(getStakingToken)] + #[storage_mapper("staking_token")] + fn staking_token(&self) -> SingleValueMapper; + + #[view(getRewardToken)] + #[storage_mapper("reward_token")] + fn reward_token(&self) -> SingleValueMapper; + + #[view(getStakedBalances)] + #[storage_mapper("staked_balances")] + fn staked_balances(&self, account: &ManagedAddress) -> SingleValueMapper; + + #[view(getStakedTimestamps)] + #[storage_mapper("staked_timestamps")] + fn staked_timestamps(&self, account: &ManagedAddress) -> SingleValueMapper; + + #[view(getStakingDuration)] + #[storage_mapper("staking_duration")] + fn staking_duration(&self) -> SingleValueMapper; + + #[init] + fn init(&self, staking_token: &TokenIdentifier, reward_token: &TokenIdentifier) { + self.staking_duration().set(30 * SECONDS_IN_DAY); + self.staking_token().set_if_empty(staking_token); + self.reward_token().set_if_empty(reward_token); + } + + #[upgrade] + fn upgrade(&self) {} + + #[payable("*")] + #[endpoint(stake)] + fn stake(&self) { + let (token_id, amount) = + self.call_value().single_fungible_esdt(); + require!(token_id == self.staking_token().get(), "Wrong token ID."); + require!(amount > BigUint::zero(), "Amount must be greater than 0"); + + let caller = + self.blockchain().get_caller(); + self.staked_balances(&caller).set(self.staked_balances(&caller).get() + &amount); + self.staked_timestamps(&caller).set(self.blockchain().get_block_timestamp()); + } + + #[payable("*")] + #[endpoint(unstake)] + fn unstake(&self) { + let caller = + self.blockchain().get_caller(); + let staked_amount = + self.staked_balances(&caller).get(); + require!(staked_amount > BigUint::zero(), "Nothing staked"); + let staked_timestamp = self.staked_timestamps(&caller).get(); + let current_timestamp = self.blockchain().get_block_timestamp(); + require!( + current_timestamp > staked_timestamp + self.staking_duration().get(), + "Staking period not over" + ); + let staking_time = current_timestamp - staked_timestamp; + let rewards = + &staked_amount * YEARLY_INTEREST * staking_time / (BPS * SECONDS_IN_YEAR); + + self.staked_balances(&caller).clear(); + + self.send().direct_esdt(&caller, &self.staking_token().get(), 0, &staked_amount); + self.send().direct_esdt(&caller, &self.reward_token().get(), 0, &rewards); + } + + #[payable("*")] + #[endpoint(add_rewards)] + fn add_rewards(&self) { + let (token_id, amount) = + self.call_value().single_fungible_esdt(); + require!(token_id == self.reward_token().get(), "Wrong token ID."); + require!(amount > BigUint::zero(), "Amount must be greater than 0"); + } + +} diff --git a/tests/syntax/uniswap_v_2_pair.rs b/tests/syntax/uniswap_v_2_pair.rs new file mode 100644 index 0000000..3fa04ac --- /dev/null +++ b/tests/syntax/uniswap_v_2_pair.rs @@ -0,0 +1,142 @@ +// This contract is a translation of UniswapV2Pair in +// https://github.com/Pi-Squared-Inc/pi2-examples/blob/fed5482baee62b60a080b1d426ffb10db463831e/solidity/src/swaps/UniswapV2Swap.sol +// +// Changes: +// * Removed the getReserves endpoint +// * Changed the timestamp computation in _update +#![no_std] + +#[allow(unused_imports)] +use multiversx_sc::imports::*; + +pub const MINIMUM_LIQUIDITY: u64 = 1000; + +#[multiversx_sc::contract] +pub trait UniswapV2Pair { + #[init] + fn init(&self, token0: &TokenIdentifier, token1: &TokenIdentifier) { + self.token0().set_if_empty(token0); + self.token1().set_if_empty(token1); + } + + #[upgrade] + fn upgrade(&self) {} + + #[view(getToken0)] + #[storage_mapper("token0")] + fn token0(&self) -> SingleValueMapper; + + #[view(getToken1)] + #[storage_mapper("token1")] + fn token1(&self) -> SingleValueMapper; + + #[view(getReserve0)] + #[storage_mapper("reserve0")] + fn reserve0(&self) -> SingleValueMapper; + + #[view(getReserve1)] + #[storage_mapper("reserve1")] + fn reserve1(&self) -> SingleValueMapper; + + #[view(getBlockTimestampLast)] + #[storage_mapper("blockTimestampLast")] + fn block_timestamp_last(&self) -> SingleValueMapper; + + #[view(getPrice0CumulativeLast)] + #[storage_mapper("price0CumulativeLast")] + fn price0_cumulative_last(&self) -> SingleValueMapper; + + #[view(getPrice1CumulativeLast)] + #[storage_mapper("price1CumulativeLast")] + fn price1_cumulative_last(&self) -> SingleValueMapper; + + #[view(getTotalSupply)] + #[storage_mapper("totalSupply")] + fn total_supply(&self) -> SingleValueMapper; + + #[view(getKLast)] + #[storage_mapper("kLast")] + fn k_last(&self) -> SingleValueMapper; + + #[endpoint] + fn swap( + &self, + amount0_out: &BigUint, + amount1_out: &BigUint, + to: &ManagedAddress + ) { + require!(amount0_out > &BigUint::zero() || amount1_out > &BigUint::zero(), "UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT"); + + let token0 = self.token0().get(); + let token1 = self.token1().get(); + let reserve0 = self.reserve0().get(); + let reserve1 = self.reserve1().get(); + + require!(amount0_out <= &reserve0 && amount1_out <= &reserve1, "UniswapV2: INSUFFICIENT_LIQUIDITY"); + + if amount0_out > &BigUint::zero() { + self.send().direct_esdt(to, &token0, 0, amount0_out); + }; + if amount1_out > &BigUint::zero() { + self.send().direct_esdt(to, &token1, 0, amount1_out); + }; + + let balance0 = self.balance(&token0); + let balance1 = self.balance(&token1); + + let amount0_in = if balance0 > &reserve0 - amount0_out { + &balance0 - &(&reserve0 - amount0_out) + } else { + BigUint::zero() + }; + let amount1_in = if balance1 > &reserve1 - amount1_out { + &balance1 - &(&reserve1 - amount1_out) + } else { + BigUint::zero() + }; + require!(amount0_in > 0 || amount1_in > 0, "UniswapV2: INSUFFICIENT_INPUT_AMOUNT"); + let balance0_adjusted = + (&balance0 * 1000_u64) - (amount0_in * 3_u64); + let balance1_adjusted = + (&balance1 * 1000_u64) - (amount1_in * 3_u64); + require!( + balance0_adjusted * balance1_adjusted >= &reserve0 * &reserve1 * 1_000_000_u64, + "UniswapV2: K" + ); + + self._update(&balance0, &balance1, &reserve0, &reserve1); + } + + #[payable("*")] + #[endpoint] + fn sync(&self) { + let token0 = self.token0().get(); + let token1 = self.token1().get(); + let balance0 = self.balance(&token0); + let balance1 = self.balance(&token1); + let reserve0 = self.reserve0().get(); + let reserve1 = self.reserve1().get(); + self._update(&balance0, &balance1, &reserve0, &reserve1); + } + + fn _update(&self, balance0: &BigUint, balance1: &BigUint, reserve0: &BigUint, reserve1: &BigUint) { + let block_timestamp = self.blockchain().get_block_timestamp(); + let last_block_timestamp = self.block_timestamp_last().get(); + let time_elapsed = block_timestamp - last_block_timestamp; + if time_elapsed > 0 && reserve0 != &BigUint::zero() && reserve1 != &BigUint::zero() { + self.price0_cumulative_last().set( + self.price0_cumulative_last().get() + (reserve1/reserve0) * time_elapsed + ); + self.price1_cumulative_last().set( + self.price1_cumulative_last().get() + (reserve0/reserve1) * time_elapsed + ); + }; + self.reserve0().set(balance0); + self.reserve1().set(balance1); + self.block_timestamp_last().set(block_timestamp); + } + + fn balance(&self, token: &TokenIdentifier) -> BigUint { + self.blockchain().get_sc_balance(&EgldOrEsdtTokenIdentifier::esdt(token.clone()), 0) + } +} diff --git a/tests/syntax/uniswap_v_2_router.rs b/tests/syntax/uniswap_v_2_router.rs new file mode 100644 index 0000000..cbf1e35 --- /dev/null +++ b/tests/syntax/uniswap_v_2_router.rs @@ -0,0 +1,290 @@ +// This contract is a translation of UniswapV2Router02 in +// https://github.com/Pi-Squared-Inc/pi2-examples/blob/fed5482baee62b60a080b1d426ffb10db463831e/solidity/src/swaps/UniswapV2Swap.sol +// +// Changes: +// * Various things related to the fact that contracts do not make transfers on +// behalf of users. In particular, `swap_tokens_for_exact_tokens` receives the +// max amount of input token, and sends the remainder to the caller. +#![no_std] + +#[allow(unused_imports)] +use multiversx_sc::imports::*; + +mod pair_proxy { + multiversx_sc::imports!(); + + #[multiversx_sc::proxy] + pub trait UniswapV2PairProxy { + #[view(getToken0)] + #[storage_mapper("token0")] + fn token0(&self) -> SingleValueMapper; + + #[view(getToken1)] + #[storage_mapper("token1")] + fn token1(&self) -> SingleValueMapper; + + #[view(getReserve0)] + #[storage_mapper("reserve0")] + fn reserve0(&self) -> SingleValueMapper; + + #[view(getReserve1)] + #[storage_mapper("reserve1")] + fn reserve1(&self) -> SingleValueMapper; + + #[endpoint] + fn swap(&self, amount0_out: &BigUint, amount1_out: &BigUint, to: &ManagedAddress); + + #[endpoint] + fn sync(&self); + } +} + +#[multiversx_sc::contract] +pub trait UniswapV2Router { + #[init] + fn init(&self) { + } + + #[upgrade] + fn upgrade(&self) {} + + #[view(getLocalPair)] + #[storage_mapper("local_pairs")] + fn local_pairs(&self, token0: &TokenIdentifier, token1: &TokenIdentifier) -> SingleValueMapper; + + #[payable("*")] + #[endpoint(swapExactTokensForTokens)] + fn swap_exact_tokens_for_tokens( + &self, + amount_out_min: BigUint, + to: &ManagedAddress, + path: MultiValueEncoded, + ) -> MultiValueEncoded { + let (token_in_id, amount_in) = self.call_value().single_fungible_esdt(); + require!(BigUint::zero() < amount_in, "Expected non-zero amount"); + + let path_vec = path.to_vec(); + + require!(path_vec.len() >= 2, "Expected non-trivial path."); + require!(path_vec.get(0).clone_value() == token_in_id, "Expected payment for the first path element"); + + let amounts = + self.uniswap_v2_library_get_amounts_out(amount_in.clone(), &path_vec); + require!(amounts.get(amounts.len() - 1).clone_value() >= amount_out_min, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT"); + + let first_pair = + self.get_local_pair_checked( + &token_in_id, + &path_vec.get(1).clone_value() + ); + self.send().direct_esdt(&first_pair, &token_in_id, 0, &amount_in); + + self._swap(&amounts, &path_vec, &to); + amounts.into() + } + + #[payable("*")] + #[endpoint(swapTokensForExactTokens)] + fn swap_tokens_for_exact_tokens( + &self, + amount_out: BigUint, + to: &ManagedAddress, + path: MultiValueEncoded, + ) -> MultiValueEncoded { + let (token_in_id, amount_in) = self.call_value().single_fungible_esdt(); + require!(BigUint::zero() < amount_in, "Expected non-zero amount"); + + let path_vec = path.to_vec(); + + require!(path_vec.len() >= 2, "Expected non-trivial path."); + require!(path_vec.get(0).clone_value() == token_in_id, "Expected payment for the first path element"); + + let amounts = + self.uniswap_v2_library_get_amounts_in(&amount_out, &path_vec); + + let amount_in_needed = amounts.get(0).clone_value(); + require!(amount_in_needed <= amount_in, "UniswapV2Router: EXCESSIVE_INPUT_AMOUNT"); + if amount_in_needed < amount_in { + self.send().direct_esdt( + &self.blockchain().get_caller(), &token_in_id, 0, &(amount_in - &amount_in_needed) + ); + }; + + let first_pair = + self.get_local_pair_checked( + &token_in_id, + &path_vec.get(1).clone_value() + ); + self.send().direct_esdt(&first_pair, &token_in_id, 0, &amount_in_needed); + + self._swap(&amounts, &path_vec, &to); + amounts.into() + } + + #[endpoint(setLocalPair)] + fn set_local_pair(&self, token_a: &TokenIdentifier, token_b: &TokenIdentifier, pair: &ManagedAddress) { + self.local_pairs(token_a, token_b).set(pair); + self.local_pairs(token_b, token_a).set(pair); + } + + #[view(getLocalPairChecked)] + fn get_local_pair_checked(&self, token_a: &TokenIdentifier, token_b: &TokenIdentifier) -> ManagedAddress { + let pair_address = self.local_pairs(token_a, token_b).get(); + require!(pair_address != ManagedAddress::zero(), "Pair not found"); + pair_address + } + + #[endpoint(syncLocalPair)] + fn sync_local_pair(&self, token_a: &TokenIdentifier, token_b: &TokenIdentifier) { + let pair_address = self.get_local_pair_checked(token_a, token_b); + self.sync(pair_address); + } + + fn _swap(&self, amounts: &ManagedVec, path: &ManagedVec, final_to: &ManagedAddress) { + for i in 0 .. path.len() - 1 { + let input = path.get(i).clone_value(); + let output = path.get(i + 1).clone_value(); + let pair_address = self.get_local_pair_checked(&input, &output); + let pair_token0 = self.token0(pair_address.clone()); + + let amount_out = amounts.get(i + 1).clone_value(); + let (amount0_out, amount1_out) = if input == pair_token0 { + (BigUint::zero(), amount_out) + } else { + (amount_out, BigUint::zero()) + }; + let to = if i < path.len() - 2 { + &self.get_local_pair_checked(&output, &path.get(i + 2).clone_value()) + } else { + final_to + }; + + self.swap(pair_address, &amount0_out, &amount1_out, to); + } + } + + fn uniswap_v2_library_get_amounts_out( + &self, amount_in: BigUint, path: &ManagedVec + ) -> ManagedVec { + require!(path.len() >= 2, "UniswapV2Library: INVALID_PATH"); + let mut amounts:ManagedVec = ManagedVec::new(); + amounts.push(amount_in); + for i in 1 .. path.len() { + let (reserve0, reserve1) = + self.uniswap_v2_library_get_reserves( + &path.get(i - 1).clone_value(), &path.get(i).clone_value() + ); + let amount = + self.uniswap_v2_library_get_amount_out(&amounts.get(i - 1), &reserve0, &reserve1); + amounts.push(amount); + }; + amounts + } + + fn uniswap_v2_library_get_amount_out(&self, amount_in: &BigUint, reserve_in: &BigUint, reserve_out: &BigUint) -> BigUint { + require!(amount_in > &BigUint::zero(), "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT"); + require!(reserve_in > &BigUint::zero() && reserve_out > &BigUint::zero(), "UniswapV2Library: INSUFFICIENT_LIQUIDITY"); + let amount_in_with_fee = amount_in * 997_u64; + let numerator = &amount_in_with_fee * reserve_out; + let denominator = reserve_in * 1000_u64 + amount_in_with_fee; + numerator / denominator + } + + fn uniswap_v2_library_get_reserves(&self, token_a: &TokenIdentifier, token_b: &TokenIdentifier) -> (BigUint, BigUint) { + let pair_address = self.get_local_pair_checked(token_a, token_b); + + if token_a == &self.token0(pair_address.clone()) { + (self.reserve0(pair_address.clone()), self.reserve1(pair_address.clone())) + } else { + (self.reserve1(pair_address.clone()), self.reserve0(pair_address)) + } + } + + fn uniswap_v2_library_get_amounts_in( + &self, amount_out: &BigUint, path: &ManagedVec + ) -> ManagedVec { + require!(path.len() >= 2, "UniswapV2Library: INVALID_PATH"); + let mut amounts:ManagedVec = ManagedVec::new(); + for _ in 0 .. path.len() { + amounts.push(BigUint::zero()); + }; + + require!(amounts.set(amounts.len() - 1, amount_out).is_ok(), "Internal error 1"); + for i in 1 .. path.len() { + let pos_out = path.len() - i; + let pos_in = pos_out - 1; + let (reserve0, reserve1) = + self.uniswap_v2_library_get_reserves( + &path.get(pos_in).clone_value(), + &path.get(pos_out).clone_value() + ); + let amount_in = + self.uniswap_v2_library_get_amount_in( + &amounts.get(pos_out).clone_value(), + &reserve0, + &reserve1 + ); + require!(amounts.set(pos_in, &amount_in).is_ok(), "Internal; error 2"); + }; + amounts + } + + fn uniswap_v2_library_get_amount_in( + &self, amount_out: &BigUint, reserve_in: &BigUint, reserve_out: &BigUint + ) -> BigUint { + require!(amount_out > &BigUint::zero(), "UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT"); + require!(reserve_in > &BigUint::zero() && reserve_out > &BigUint::zero(), "UniswapV2Library: INSUFFICIENT_LIQUIDITY"); + let numerator = reserve_in * amount_out * 1000_u64; + let denominator = (reserve_out - amount_out) * 997_u64; + if denominator != 0 { + (numerator / denominator) + 1_u64 + } else { + BigUint::from(1_u64) + } + } + + #[proxy] + fn pair_proxy(&self, sc_address: ManagedAddress) -> pair_proxy::Proxy; + + fn token0(&self, pair: ManagedAddress) -> TokenIdentifier { + self.pair_proxy(pair) + .token0() + .execute_on_dest_context() + } + + fn reserve0(&self, pair: ManagedAddress) -> BigUint { + self.pair_proxy(pair) + .reserve0() + .execute_on_dest_context() + } + + fn reserve1(&self, pair: ManagedAddress) -> BigUint { + self.pair_proxy(pair) + .reserve1() + .execute_on_dest_context() + } + + fn swap( + &self, + pair: ManagedAddress, + amount0_out: &BigUint, + amount1_out: &BigUint, + to: &ManagedAddress + ) { + let _: IgnoreValue = self + .pair_proxy(pair) + .swap(amount0_out, amount1_out, to) + .execute_on_dest_context(); + } + + fn sync( + &self, + pair: ManagedAddress, + ) { + let _: IgnoreValue = self + .pair_proxy(pair) + .sync() + .execute_on_dest_context(); + } + +} diff --git a/tests/syntax/uniswap_v_2_swap.rs b/tests/syntax/uniswap_v_2_swap.rs new file mode 100644 index 0000000..6ff59c6 --- /dev/null +++ b/tests/syntax/uniswap_v_2_swap.rs @@ -0,0 +1,177 @@ +// This contract is a translation of UniswapV2Swap in +// https://github.com/Pi-Squared-Inc/pi2-examples/blob/fed5482baee62b60a080b1d426ffb10db463831e/solidity/src/swaps/UniswapV2Swap.sol +// +// Changes: +// * Initializaion does not add the router's pairs, they must be initialized +// and added separately. +// * Various things related to the fact that contracts do not make transfers on +// behalf of users and must receive the tokens with the call. +#![no_std] + +#[allow(unused_imports)] +use multiversx_sc::imports::*; + +mod router_proxy { + multiversx_sc::imports!(); + + #[multiversx_sc::proxy] + pub trait UniswapV2RouterProxy { + #[payable("*")] + #[endpoint(swapExactTokensForTokens)] + fn swap_exact_tokens_for_tokens( + &self, + amount_out_min: BigUint, + to: &ManagedAddress, + path: MultiValueEncoded, + ) -> MultiValueEncoded; + + #[payable("*")] + #[endpoint(swapTokensForExactTokens)] + fn swap_tokens_for_exact_tokens( + &self, + amount_out: BigUint, + to: &ManagedAddress, + path: MultiValueEncoded, + ) -> MultiValueEncoded; + } +} + + +#[multiversx_sc::contract] +pub trait UniswapV2Swap { + #[view(getWeth)] + #[storage_mapper("weth")] + fn weth(&self) -> SingleValueMapper; + + #[view(getDai)] + #[storage_mapper("dai")] + fn dai(&self) -> SingleValueMapper; + + #[view(getUsdc)] + #[storage_mapper("usdc")] + fn usdc(&self) -> SingleValueMapper; + + #[view(getRouter)] + #[storage_mapper("router")] + fn router(&self) -> SingleValueMapper; + + #[init] + fn init(&self, weth: &TokenIdentifier, dai: &TokenIdentifier, usdc: &TokenIdentifier, router: &ManagedAddress) { + self.weth().set_if_empty(weth); + self.dai().set_if_empty(dai); + self.usdc().set_if_empty(usdc); + + self.router().set_if_empty(router); + } + + #[upgrade] + fn upgrade(&self) {} + + + #[payable("*")] + #[endpoint(swapExactTokensForTokens)] + fn swap_single_hop_exact_amount_in(&self, amount_out_min: &BigUint) -> BigUint { + let (token_in_id, amount_in) = self.call_value().single_fungible_esdt(); + require!(BigUint::zero() < amount_in, "Expected non-zero amount"); + require!(token_in_id == self.weth().get(), "Expecting weth transfer"); + + let mut path: ManagedVec = ManagedVec::new(); + path.push(self.weth().get()); + path.push(self.dai().get()); + + let path_encoded: MultiValueEncoded = path.into(); + let amounts: MultiValueEncoded = self.router_proxy(self.router().get()) + .swap_exact_tokens_for_tokens(amount_out_min, self.blockchain().get_caller(), path_encoded) + .esdt((token_in_id, 0, amount_in)) + .execute_on_dest_context(); + let amounts_vec = amounts.to_vec(); + + amounts_vec.get(1).clone_value() + } + + #[payable("*")] + #[endpoint(swapMultiHopExactAmountIn)] + fn swap_multi_hop_exact_amount_in(&self, amount_out_min: &BigUint) -> BigUint { + let (token_in_id, amount_in) = self.call_value().single_fungible_esdt(); + require!(BigUint::zero() < amount_in, "Expected non-zero amount"); + require!(token_in_id == self.dai().get(), "Expecting dai transfer"); + + let mut path: ManagedVec = ManagedVec::new(); + path.push(self.dai().get()); + path.push(self.weth().get()); + path.push(self.usdc().get()); + + let path_encoded: MultiValueEncoded = path.into(); + let amounts: MultiValueEncoded = self.router_proxy(self.router().get()) + .swap_exact_tokens_for_tokens(amount_out_min, self.blockchain().get_caller(), path_encoded) + .esdt((token_in_id, 0, amount_in)) + .execute_on_dest_context(); + let amounts_vec = amounts.to_vec(); + + amounts_vec.get(2).clone_value() + } + + #[payable("*")] + #[endpoint(swapSingleHopExactAmountOut)] + fn swap_single_hop_exact_amount_out(&self, amount_out_desired: &BigUint) -> BigUint { + let (token_in_id, amount_in) = self.call_value().single_fungible_esdt(); + require!(BigUint::zero() < amount_in, "Expected non-zero amount"); + require!(token_in_id == self.weth().get(), "Expecting weth transfer"); + + let mut path: ManagedVec = ManagedVec::new(); + path.push(self.weth().get()); + path.push(self.dai().get()); + + let path_encoded: MultiValueEncoded = path.into(); + let amounts: MultiValueEncoded = self.router_proxy(self.router().get()) + .swap_tokens_for_exact_tokens(amount_out_desired, self.blockchain().get_caller(), path_encoded) + .esdt((token_in_id.clone(), 0, amount_in.clone())) + .execute_on_dest_context(); + let amounts_vec = amounts.to_vec(); + + let amount_used = amounts_vec.get(0).clone_value(); + if amount_used < amount_in { + self.send().direct_esdt( + &self.blockchain().get_caller(), &token_in_id, 0, &(amount_in - &amount_used) + ); + }; + + amounts_vec.get(1).clone_value() + } + + #[payable("*")] + #[endpoint(swapMultiHopExactAmountOut)] + fn swap_multi_hop_exact_amount_out( + &self, amount_out_desired: &BigUint + ) -> BigUint { + let (token_in_id, amount_in) = self.call_value().single_fungible_esdt(); + require!(BigUint::zero() < amount_in, "Expected non-zero amount"); + require!(token_in_id == self.dai().get(), "Expecting dai transfer"); + + let mut path: ManagedVec = ManagedVec::new(); + path.push(self.dai().get()); + path.push(self.weth().get()); + path.push(self.usdc().get()); + + let path_encoded: MultiValueEncoded = path.into(); + let amounts: MultiValueEncoded = self.router_proxy(self.router().get()) + .swap_tokens_for_exact_tokens(amount_out_desired, self.blockchain().get_caller(), path_encoded) + .esdt((token_in_id.clone(), 0, amount_in.clone())) + .execute_on_dest_context(); + let amounts_vec = amounts.to_vec(); + + let amount_used = amounts_vec.get(0).clone_value(); + if amount_used < amount_in { + self.send().direct_esdt( + &self.blockchain().get_caller(), &token_in_id, 0, &(amount_in - &amount_used) + ); + }; + + amounts_vec.get(2).clone_value() + } + + + #[proxy] + fn router_proxy(&self, sc_address: ManagedAddress) -> router_proxy::Proxy; + +}