diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index b513f098..00000000 --- a/INSTALL.md +++ /dev/null @@ -1,114 +0,0 @@ -This guides you through installing Flint in its current state on Linux - -**Contents** -1. Prerequisites -2. Installation -3. Docker -4. Usage - -## Prerequisites -The following must be installed to build Flint: -* mono 5.20 or later (C# 7.0) -* swiftenv -* clang -* nodejs npm - -### Additionally for testing -To run the testing libraries, install: -* truffle 4 - -### On Ubuntu 18.04 LTS -This assumes a standard Ubuntu build with `apt`, `wget`, `curl`, `gnupg`, `ca-certificates` and `git` installed. If you don't have one of them installed, you should be notified during the process. If you have any kind of error, try installing them. Note Ubuntu 16.04 has different installation procedures when using apt and installing Mono, thus amendments will need to be made to this process. -```bash -sudo apt install nodejs npm clang - -# Mono - https://www.mono-project.com/download/stable/ -sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF -echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" \ - | sudo tee /etc/apt/sources.list.d/mono-official-stable.list -sudo apt update -sudo apt install mono-devel - -# Swiftenv - https://swiftenv.fuller.li/en/latest/installation.html -git clone https://github.com/kylef/swiftenv.git ~/.swiftenv -echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile -echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile -echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile -``` - -## Installation -In your terminal, run the following commands -```bash -# Use -jN for multi-core speedup (N >= 2) -git clone --recurse-submodule https://github.com/flintlang/flint.git -cd flint -# No need iff swiftenv has already installed relevent swift version or not using swiftenv -swiftenv install -swift package update -# Create a FLINTPATH for the compiler to run (this may be removed in a future version) -echo "export FLINTPATH=\"$(pwd)\"" >> ~/.bash_profile -source ~/.bash_profile - -make -``` - -## Docker -To run the environment without doing any package installations: -```bash -git clone --recurse-submodule https://github.com/flintlang/flint.git -cd flint -sudo docker build -t "flint_docker" . -### ---------------------------------------------- ### -# Docker will build, this process may take some time # -### ---------------------------------------------- ### -sudo docker run --privileged -i -t flint_docker -# Then, inside the docker container, run -source ~/.bash_profile -``` - -## Usage -To use flint to compile a flint contract (in this example `counter.flint`) into solidity code run the following code from inside the flint project folder: -```bash -export FLINTPATH=$(pwd) -export PATH=$FLINTPATH/.build/debug:$PATH -flintc --emit-ir --ir-output ./bin examples/valid/counter.flint -``` -This will generate a main.sol file inside the bin sub-directory which can then be compiled to be depolyed on the Etherum blockchain. If you wish to have the output file in the current directory remove bin from the previous command: -``` -flintc --emit-ir --ir-output ./bin examples/valid/counter.flint -``` - -To test it, we recommend using Remix IDE, following these instructions https://docs.flintlang.org/docs/language_guide#remix-integration - -## macOS - -This guides you through installing Flint in its current state on macOS. You can use the Docker instructions above if you would prefer to have a docker container. - -## Prerequisites -The following must be installed to build Flint on Macs: -* xcode - preferences/Locations/Command Line tools must not be empty (the default) -* homebrew - https://brew.sh, update brew if it isn't new with brew update -* brew install node - get node and npm if you don't have them -* brew install wget - get wget if you don't have it -* brew cask install mono-mdk -* install swiftenv - here is a script to do this: -``` -git clone https://github.com/kylef/swiftenv.git ~/.swiftenv -echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile -echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile -echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile -``` -## Installation -```` -git clone --recurse-submodule https://github.com/flintlang/flint.git -cd flint -# Create a FLINTPATH for the compiler to run (this may be removed in a future version) -echo "export FLINTPATH=\"$(pwd)\"" >> ~/.bash_profile -source ~/.bash_profile - -make -```` - -## Usage - -Same as for linux above. diff --git a/Sources/AST/Declaration/TraitDeclaration.swift b/Sources/AST/Declaration/TraitDeclaration.swift index dac3ca49..f105ab42 100644 --- a/Sources/AST/Declaration/TraitDeclaration.swift +++ b/Sources/AST/Declaration/TraitDeclaration.swift @@ -14,20 +14,20 @@ public struct TraitDeclaration: ASTNode { public var identifier: Identifier // TODO: public var states: [TypeState] public var members: [TraitMember] - public var moduleAddress: String? // Move-specific + public var decorators: [FunctionCall] // Move-specific public init( traitKind: Token, traitToken: Token, identifier: Identifier, members: [TraitMember], - moduleAddress: String? = nil + decorators: [FunctionCall] = [] ) { self.traitKind = traitKind self.traitToken = traitToken self.identifier = identifier self.members = members - self.moduleAddress = moduleAddress + self.decorators = decorators } // MARK: - ASTNode diff --git a/Sources/AST/Environment/Environment+Additions.swift b/Sources/AST/Environment/Environment+Additions.swift index 27876681..68218177 100644 --- a/Sources/AST/Environment/Environment+Additions.swift +++ b/Sources/AST/Environment/Environment+Additions.swift @@ -263,6 +263,9 @@ extension Environment { let special = externalTraitInitializer(trait) addInitializerSignature(special, enclosingType: trait.identifier.name, callerProtections: [], generated: true) + if !trait.decorators.isEmpty { + types[trait.identifier.name]?.decorators = trait.decorators + } } else { isExternal = false } diff --git a/Sources/AST/Environment/Information/CallableInformation.swift b/Sources/AST/Environment/Information/CallableInformation.swift index bba58f65..1bbe557f 100644 --- a/Sources/AST/Environment/Information/CallableInformation.swift +++ b/Sources/AST/Environment/Information/CallableInformation.swift @@ -10,7 +10,7 @@ public enum CallableInformation { case functionInformation(FunctionInformation) case specialInformation(SpecialInformation) - var parameterTypes: [RawType] { + public var parameterTypes: [RawType] { switch self { case .functionInformation(let functionInformation): return functionInformation.parameterTypes diff --git a/Sources/AST/Environment/Information/TypeInformation.swift b/Sources/AST/Environment/Information/TypeInformation.swift index fdcdfd8f..7b5c6160 100644 --- a/Sources/AST/Environment/Information/TypeInformation.swift +++ b/Sources/AST/Environment/Information/TypeInformation.swift @@ -16,6 +16,7 @@ public struct TypeInformation { public var publicInitializer: SpecialDeclaration? var publicFallback: SpecialDeclaration? var conformances: [TypeInformation] = [] + public var decorators: [FunctionCall]? public var allFunctions: [String: [FunctionInformation]] { return conformances.map({ $0.functions }).reduce(functions, +) diff --git a/Sources/Compiler/Compiler.swift b/Sources/Compiler/Compiler.swift index 9d3ec3d0..e7ca5f23 100644 --- a/Sources/Compiler/Compiler.swift +++ b/Sources/Compiler/Compiler.swift @@ -765,6 +765,16 @@ public enum CompilerTarget { case .move: return "mvir" } } + + public func configureSemanticAnalyser(semanticAnalyser: SemanticAnalyzer) -> SemanticAnalyzer { + switch self { + case .evm: return semanticAnalyser + case .move: + var semanticAnalyser = semanticAnalyser + semanticAnalyser.allowExternalStructs = true + return semanticAnalyser + } + } } public struct CompilerConfiguration { @@ -813,8 +823,14 @@ public struct CompilerConfiguration { self.maxTransactionDepth = maxTransactionDepth self.skipVerifier = skipVerifier self.skipCodeGen = skipCodeGen - self.diagnostics = diagnostics - self.astPasses = astPasses ?? (Compiler.defaultASTPasses + (skipVerifier ? [] : Compiler.verifierASTPasses)) + self.diagnostics = diagnostics //Compiler.defaultASTPasses + self.astPasses = (astPasses ?? (Compiler.defaultASTPasses + (skipVerifier ? [] : Compiler.verifierASTPasses))) + .map { (pass: ASTPass) in + if let pass = pass as? SemanticAnalyzer { + return target.configureSemanticAnalyser(semanticAnalyser: pass) + } + return pass + } self.stdLib = stdLib self.target = target } diff --git a/Sources/Compiler/Target.swift b/Sources/Compiler/Target.swift index 78a44095..207d055c 100644 --- a/Sources/Compiler/Target.swift +++ b/Sources/Compiler/Target.swift @@ -96,7 +96,10 @@ public class MoveTarget: Target { } exit(0) } - let generator = MoveGenerator(ast: irPreprocessOutcome.element, environment: environment) + + let generator = MoveGenerator(ast: irPreprocessOutcome.element, + environment: environment, + sourceContext: sourceContext) return generator.generateCode() } } diff --git a/Sources/MoveGen/AST/FunctionDeclaration.swift b/Sources/MoveGen/AST/FunctionDeclaration.swift index b801a627..f95fcea3 100644 --- a/Sources/MoveGen/AST/FunctionDeclaration.swift +++ b/Sources/MoveGen/AST/FunctionDeclaration.swift @@ -120,7 +120,7 @@ extension AST.FunctionDeclaration { } let args: [FunctionArgument] = signature.parameters.map { parameter in - return FunctionArgument(.identifier(parameter.identifier)) + FunctionArgument(.identifier(parameter.identifier)) } let functionCallExpr: Expression = .functionCall( diff --git a/Sources/MoveGen/AST/TraitDeclaration.swift b/Sources/MoveGen/AST/TraitDeclaration.swift new file mode 100644 index 00000000..adcf9ff1 --- /dev/null +++ b/Sources/MoveGen/AST/TraitDeclaration.swift @@ -0,0 +1,37 @@ +// +// Created by matthewross on 31/08/19. +// + +import Foundation +import AST + +extension TraitDeclaration { + var moduleAddress: String? { + guard let argument: FunctionArgument = decorators.first(where: { $0.identifier.name == "module" })?.arguments[0], + let name = argument.identifier?.name, + name == "address", + case .literal(let token) = argument.expression, + case .literal(.address(let address)) = token.kind else { + return nil + } + return address + } + + var isModule: Bool { + return decorators.contains(where: { $0.identifier.name == "module" }) + } + + var associatedModule: String? { + guard let argument = decorators.first(where: { $0.identifier.name == "associated" })?.arguments[0], + let name = argument.identifier?.name, + name == "", + case .identifier(let identifier) = argument.expression else { + return nil + } + return identifier.name + } + + var isStruct: Bool { + return decorators.contains(where: { $0.identifier.name == "data" }) + } +} diff --git a/Sources/MoveGen/AST/Type.swift b/Sources/MoveGen/AST/Type.swift index 8f113f3f..9a35ab4c 100644 --- a/Sources/MoveGen/AST/Type.swift +++ b/Sources/MoveGen/AST/Type.swift @@ -8,15 +8,17 @@ import AST extension RawType { - public func isExternalTraitType(environment: Environment) -> Bool { + public func isExternalContract(environment: Environment) -> Bool { var internalRawType: RawType = self while case .inoutType(let inoutType) = internalRawType { internalRawType = inoutType } if case .userDefinedType(let typeIdentifier) = internalRawType { return environment.isExternalTraitDeclared(typeIdentifier) + && !(environment.types[typeIdentifier].flatMap { (information: TypeInformation) in + information.decorators?.contains(where: { $0.identifier.name == "data" }) + } ?? false) } - return false } } diff --git a/Sources/MoveGen/Component/MoveSelf.swift b/Sources/MoveGen/Component/MoveSelf.swift index 452c8280..009842e5 100644 --- a/Sources/MoveGen/Component/MoveSelf.swift +++ b/Sources/MoveGen/Component/MoveSelf.swift @@ -10,6 +10,7 @@ import Lexer import MoveIR import Source import AST +import Diagnostic /// Generates code for a "self" expression. struct MoveSelf { @@ -32,14 +33,21 @@ struct MoveSelf { fatalError("Unexpected token \(token.kind)") } guard !functionContext.isConstructor else { - print(#""" - \#u{001B}[1;38;5;196mMoveIR generation error:\#u{001B}[0m \# - `self' reference before all fields initialized in function `init' in \#(token.sourceLocation) - \#tCannot use `self' in a constructor before all attributes have been assigned to, \# - as some are still unitialized. This includes any method calls which could access instance fields. - \#tInstead try moving method calls to after all values have been initialized. - """#) - exit(1) + Diagnostics.add(Diagnostic(severity: .error, + sourceLocation: token.sourceLocation, + message: "`self' reference before all fields initialized in function `init'")) + Diagnostics.add(Diagnostic(severity: .note, + sourceLocation: token.sourceLocation, + message: #""" + Cannot use `self' in a constructor before all \# + attributes have been assigned to, \# + as some are still unitialised. This includes any \# + method calls which could access instance fields. \# + + Instead try moving method calls to after all values \# + have been initialized. + """#)) + Diagnostics.displayAndExit() } if position == .left { diff --git a/Sources/MoveGen/Declaration/MoveVariableDeclaration.swift b/Sources/MoveGen/Declaration/MoveVariableDeclaration.swift index 4b8cf1fc..bb0495d4 100644 --- a/Sources/MoveGen/Declaration/MoveVariableDeclaration.swift +++ b/Sources/MoveGen/Declaration/MoveVariableDeclaration.swift @@ -8,6 +8,7 @@ import Foundation import AST import MoveIR +import Diagnostic /// Generates code for a variable declaration. struct MoveVariableDeclaration { @@ -18,15 +19,12 @@ struct MoveVariableDeclaration { from: variableDeclaration.type.rawType, environment: functionContext.environment )?.render(functionContext: functionContext) else { - print(""" - Cannot get variable declaration type from \(variableDeclaration.type.rawType) \ - at position \(variableDeclaration.sourceLocation) for variable \(variableDeclaration.identifier.name). - - \(functionContext.blockStack) - - flintc internal error located at \(#file):\(#line) - """) - exit(1) + Diagnostics.add(Diagnostic( + severity: .error, + sourceLocation: variableDeclaration.sourceLocation, + message: "Cannot get variable declaration type from \(variableDeclaration.type.rawType)" + )) + Diagnostics.displayAndExit() } guard !variableDeclaration.identifier.isSelf else { return .variableDeclaration(MoveIR.VariableDeclaration((MoveSelf.name, typeIR))) diff --git a/Sources/MoveGen/Diagnostics/Diagnostics.swift b/Sources/MoveGen/Diagnostics/Diagnostics.swift new file mode 100644 index 00000000..49e2c0e6 --- /dev/null +++ b/Sources/MoveGen/Diagnostics/Diagnostics.swift @@ -0,0 +1,21 @@ +import Diagnostic +import Foundation + +class Diagnostics { + public static var diagnostics: [Diagnostic] = [] + public static var sourceContext: SourceContext? + + static func add(_ diagnostics: Diagnostic...) { + self.diagnostics.append(contentsOf: diagnostics) + } + + static func display() { + try! print(DiagnosticsFormatter(diagnostics: diagnostics, + sourceContext: sourceContext!).rendered()) + } + + static func displayAndExit(code: Int32 = 1) -> Never { + display() + return exit(code) + } +} diff --git a/Sources/MoveGen/Expression/MoveExternalCall.swift b/Sources/MoveGen/Expression/MoveExternalCall.swift index c85f2cc3..a1035042 100644 --- a/Sources/MoveGen/Expression/MoveExternalCall.swift +++ b/Sources/MoveGen/Expression/MoveExternalCall.swift @@ -21,23 +21,18 @@ struct MoveExternalCall { var lookupCall = functionCall // remove external contract address from lookup lookupCall.arguments.removeFirst() - guard case .matchedFunction(let matchingFunction) = - functionContext.environment.matchFunctionCall(lookupCall, - enclosingType: functionCall.identifier.enclosingType ?? - functionContext.enclosingTypeName, - typeStates: [], - callerProtections: [], - scopeContext: functionContext.scopeContext) else { - fatalError("cannot match function signature in external call") - } - - matchingFunction.parameterTypes.forEach { paremeterType in - switch paremeterType { - case .basicType, .externalType: - break - default: - fatalError("cannot use non-basic type in external call") - } + let matched = functionContext.environment.matchFunctionCall(lookupCall, + enclosingType: functionCall.identifier.enclosingType ?? + functionContext.enclosingTypeName, + typeStates: [], + callerProtections: [], + scopeContext: functionContext.scopeContext) + if case .matchedFunction = matched { + } else if case .failure(let candidates) = matched, + let candidate: CallableInformation = candidates.first, + case .functionInformation = candidate { + } else { + fatalError("cannot match function signature `\(lookupCall)' in external call") } switch externalCall.mode { diff --git a/Sources/MoveGen/Expression/MoveFunctionCall.swift b/Sources/MoveGen/Expression/MoveFunctionCall.swift index ff1dd261..05f621a5 100644 --- a/Sources/MoveGen/Expression/MoveFunctionCall.swift +++ b/Sources/MoveGen/Expression/MoveFunctionCall.swift @@ -37,18 +37,27 @@ struct MoveFunctionCall { lookupCall.arguments.remove(at: 0) } - if environment.isExternalTraitInitializer(functionCall: lookupCall) { - let externalContractAddress = lookupCall.arguments[0].expression - return MoveExpression(expression: externalContractAddress, position: .normal) - .rendered(functionContext: functionContext) + var moduleName = self.moduleName + var callName = functionCall.mangledIdentifier ?? functionCall.identifier.name + + if environment.isExternalTraitInitializer(functionCall: lookupCall), + let trait: TypeInformation = environment.types[lookupCall.identifier.name] { + if trait.decorators?.contains(where: { $0.identifier.name == "data" }) ?? false { + moduleName = lookupCall.identifier.name + callName = "new" + } else { + let externalContractAddress = lookupCall.arguments[0].expression + return MoveExpression(expression: externalContractAddress, position: .normal) + .rendered(functionContext: functionContext) + } } let args: [MoveIR.Expression] = functionCall.arguments.map({ (argument: FunctionArgument) in - return MoveExpression(expression: argument.expression, position: .normal) + MoveExpression(expression: argument.expression, position: .normal) .rendered(functionContext: functionContext) }) - let identifier = "\(moduleName).\(functionCall.mangledIdentifier ?? functionCall.identifier.name)" + let identifier = "\(moduleName).\(callName)" return .functionCall(MoveIR.FunctionCall(identifier, args)) } } diff --git a/Sources/MoveGen/MoveCanonicalType.swift b/Sources/MoveGen/MoveCanonicalType.swift index a16f2125..b7bd8c34 100644 --- a/Sources/MoveGen/MoveCanonicalType.swift +++ b/Sources/MoveGen/MoveCanonicalType.swift @@ -8,6 +8,7 @@ import Foundation import AST import MoveIR +import Diagnostic /// A MoveIR type. indirect enum CanonicalType: CustomStringConvertible { @@ -17,6 +18,7 @@ indirect enum CanonicalType: CustomStringConvertible { case bytearray case resource(String) case `struct`(String) + case external(String, CanonicalType) // case reference(CanonicalType) Not Yet Used case mutableReference(CanonicalType) @@ -29,21 +31,32 @@ indirect enum CanonicalType: CustomStringConvertible { case .bool: self = .bool case .string: self = .bytearray default: - print("rawType: \(rawType)") + Diagnostics.add(Diagnostic(severity: .warning, + sourceLocation: nil, + message: "Could not detect basic type for `\(rawType)'")) return nil } case .userDefinedType(let identifier): - if rawType.isCurrencyType || (environment?.isContractDeclared(identifier) ?? false) { + if let environment = environment, + CanonicalType.isResourceType(rawType, identifier: identifier, environment: environment) { self = .resource(identifier) - } else if environment?.isExternalTraitDeclared(identifier) ?? false { + } else if let environment = environment, + rawType.isExternalContract(environment: environment) { self = .address + } else if environment?.isExternalTraitDeclared(identifier) ?? false, + let type: TypeInformation = environment?.types[identifier] { + if type.decorators?.contains(where: { $0.identifier.name == "resource" }) ?? false { + self = .external(identifier, .resource("T")) + } else { + self = .external(identifier, .`struct`("T")) + } } else if let environment = environment, - environment.isEnumDeclared(rawType.name), - let type: TypeInformation = environment.types[rawType.name], - let value: AST.Expression = type.properties.values.first?.property.value { + environment.isEnumDeclared(identifier), + let type: TypeInformation = environment.types[identifier], + let value: AST.Expression = type.properties.values.first?.property.value { self = CanonicalType(from: environment.type(of: value, - enclosingType: rawType.name, + enclosingType: identifier, scopeContext: ScopeContext()))! } else { self = .struct(identifier) @@ -60,20 +73,29 @@ indirect enum CanonicalType: CustomStringConvertible { case .dictionaryType(let key, _): self = CanonicalType(from: key)! default: - print("rawType': \(rawType)") + Diagnostics.add(Diagnostic(severity: .warning, + sourceLocation: nil, + message: "Could not detect type for `\(rawType)'")) return nil } } + private static func isResourceType(_ rawType: AST.RawType, + identifier: RawTypeIdentifier, + environment: Environment) -> Bool { + return rawType.isCurrencyType || environment.isContractDeclared(identifier) + } + public var description: String { switch self { case .address: return "CanonicalType.address" case .u64: return "CanonicalType.u64" case .bool: return "CanonicalType.bool" case .bytearray: return "CanonicalType.bytearray" - case .resource(let name): return "CanonicalType.R#\(name)" - case .struct(let name): return "CanonicalType.V#\(name)" + case .resource(let name): return "CanonicalType.Resource.\(name)" + case .struct(let name): return "CanonicalType.Struct.\(name)" case .mutableReference(let type): return "CanonicalType.&mut \(type)" + case .external(let module, let type): return "CanonicalType.External(\(module), \(type))" } } @@ -91,7 +113,12 @@ indirect enum CanonicalType: CustomStringConvertible { return .resource(name: "\(name).T") case .mutableReference(let type): return .mutableReference(to: type.render(functionContext: functionContext)) + case .external(let module, let type): + switch type { + case .resource(let name): return .resource(name: "\(module).\(name)") + case .`struct`(let name): return .`struct`(name: "\(module).\(name)") + default: fatalError("Only external structs and resources are allowed") + } } - } } diff --git a/Sources/MoveGen/MoveContract.swift b/Sources/MoveGen/MoveContract.swift index 7d2c3601..993619e2 100644 --- a/Sources/MoveGen/MoveContract.swift +++ b/Sources/MoveGen/MoveContract.swift @@ -13,7 +13,6 @@ import MoveIR struct MoveContract { static var stateVariablePrefix = "flintState$" - static var reentrancyProtectorValue = 10000 var contractDeclaration: ContractDeclaration var contractBehaviorDeclarations: [ContractBehaviorDeclaration] @@ -21,6 +20,14 @@ struct MoveContract { var environment: Environment var externalTraitDeclarations: [TraitDeclaration] + var externalModules: [TraitDeclaration] { + return externalTraitDeclarations.filter { $0.isModule } + } + + var externalStructs: [TraitDeclaration] { + return externalTraitDeclarations.filter { $0.isStruct } + } + init(contractDeclaration: ContractDeclaration, contractBehaviorDeclarations: [ContractBehaviorDeclaration], structDeclarations: [StructDeclaration], @@ -34,12 +41,16 @@ struct MoveContract { } func rendered() -> String { - let imports: [MoveIR.Statement] = externalTraitDeclarations - .map { .`import`(ModuleImport(name: $0.identifier.name, address: $0.moduleAddress!)) } + let imports: [MoveIR.Statement] = externalTraitDeclarations.compactMap { (declaration: TraitDeclaration) in + declaration.moduleAddress.map { + .`import`(ModuleImport(name: declaration.identifier.name, address: $0)) + } + } let renderedImports = MoveIR.Statement.renderStatements(statements: imports) + // Generate code for each function in the contract. let functions = contractBehaviorDeclarations.flatMap { contractBehaviorDeclaration in - return contractBehaviorDeclaration.members.compactMap { member -> MoveFunction? in + contractBehaviorDeclaration.members.compactMap { member -> MoveFunction? in guard case .functionDeclaration(let functionDeclaration) = member else { return nil } diff --git a/Sources/MoveGen/MoveGenerator.swift b/Sources/MoveGen/MoveGenerator.swift index 0464000f..39a19066 100644 --- a/Sources/MoveGen/MoveGenerator.swift +++ b/Sources/MoveGen/MoveGenerator.swift @@ -4,14 +4,16 @@ import Foundation import AST +import Diagnostic public struct MoveGenerator { var topLevelModule: TopLevelModule var environment: Environment - public init(ast topLevelModule: TopLevelModule, environment: Environment) { + public init(ast topLevelModule: TopLevelModule, environment: Environment, sourceContext: SourceContext) { self.topLevelModule = topLevelModule self.environment = environment + Diagnostics.sourceContext = sourceContext } public func generateCode() -> String { diff --git a/Sources/MoveGen/Preprocessor/MovePreprocessor.swift b/Sources/MoveGen/Preprocessor/MovePreprocessor.swift index 354ea992..f3c295e6 100644 --- a/Sources/MoveGen/Preprocessor/MovePreprocessor.swift +++ b/Sources/MoveGen/Preprocessor/MovePreprocessor.swift @@ -10,6 +10,7 @@ import AST import Foundation import Source import Lexer +import Diagnostic /// A preprocessing step to update the program's AST before code generation. public struct MovePreprocessor: ASTPass { @@ -37,7 +38,7 @@ public struct MovePreprocessor: ASTPass { public func process(type: Type, passContext: ASTPassContext) -> ASTPassResult { // Used to turn external trait types into addresses var type = type - if let environment = passContext.environment, type.rawType.isExternalTraitType(environment: environment) { + if let environment = passContext.environment, type.rawType.isExternalContract(environment: environment) { type.rawType = RawType.externalTraitType } return ASTPassResult(element: type, diagnostics: [], passContext: passContext) @@ -104,14 +105,16 @@ extension ASTPass { guard let environment = passContext.environment, var scopeContext = passContext.scopeContext, let enclosingType = passContext.enclosingTypeIdentifier?.name else { - print("Cannot infer type for \(element.sourceLocation)") - exit(1) + Diagnostics.add(Diagnostic(severity: .error, + sourceLocation: element.sourceLocation, + message: "Insufficient information to deduce type")) + Diagnostics.displayAndExit() } var type: RawType = environment.type(of: element, enclosingType: enclosingType, scopeContext: scopeContext) - if type.isExternalTraitType(environment: environment) { + if type.isExternalContract(environment: environment) { type = RawType.externalTraitType } diff --git a/Sources/Parser/Parser+Declaration.swift b/Sources/Parser/Parser+Declaration.swift index 90e0fd1d..6495a48a 100644 --- a/Sources/Parser/Parser+Declaration.swift +++ b/Sources/Parser/Parser+Declaration.swift @@ -49,7 +49,7 @@ extension Parser { case .identifier, .`self`: let contractBehaviorDeclaration = try parseContractBehaviorDeclaration() declarations.append(.contractBehaviorDeclaration(contractBehaviorDeclaration)) - case .external: + case .external, .punctuation(.at): let externalTraitDeclaration = try parseTraitDeclaration() declarations.append(.traitDeclaration(externalTraitDeclaration)) default: @@ -119,18 +119,13 @@ extension Parser { } func parseTraitDeclaration() throws -> TraitDeclaration { + let decorations: [FunctionCall] = attempt { + return try parseDecorators() + } ?? [] + let traitKind = try consume(anyOf: [.struct, .contract, .external], or: .badDeclaration(at: latestSource)) let traitToken = try consume(.trait, or: .badDeclaration(at: latestSource)) - // This is move-specific syntax required to get module address that a Move module defining - // the external trait is published at - let moduleAddress: String? = attempt { - let moduleAddressToken: Token = try parseLiteral() - try consume(.punctuation(.dot), or: .expectedMoveModuleAddress(at: latestSource)) - if case .literal(.address(let addressStr)) = moduleAddressToken.kind { - return addressStr - } - throw raise(.expectedMoveModuleAddress(at: latestSource)) - } + let identifier = try parseIdentifier() try consume(.punctuation(.openBrace), or: .leftBraceExpected(in: "trait declaration", at: latestSource)) let traitMembers = try parseTraitMembers() @@ -141,10 +136,25 @@ extension Parser { traitToken: traitToken, identifier: identifier, members: traitMembers, - moduleAddress: moduleAddress + decorators: decorations ) } + func parseDecorators() throws -> [FunctionCall] { + var decorators = [FunctionCall]() + while currentToken?.kind == .punctuation(.at) { + try consume(.punctuation(.at), or: .expectedValidOperator(at: latestSource)) + let decorator = try attempt { + return try parseFunctionCall() + } ?? FunctionCall(identifier: parseIdentifier(), + arguments: [], + closeBracketToken: Token.DUMMY, + isAttempted: false) + decorators.append(decorator) + } + return decorators + } + func parseEventDeclaration() throws -> EventDeclaration { let eventToken = try consume(.event, or: .badDeclaration(at: latestSource)) let identifier = try parseIdentifier() diff --git a/Sources/SemanticAnalyzer/SemanticAnalyzer+Components.swift b/Sources/SemanticAnalyzer/SemanticAnalyzer+Components.swift index 2b431496..ca92efa7 100644 --- a/Sources/SemanticAnalyzer/SemanticAnalyzer+Components.swift +++ b/Sources/SemanticAnalyzer/SemanticAnalyzer+Components.swift @@ -175,7 +175,7 @@ extension SemanticAnalyzer { diagnostics: inout [Diagnostic]) { if let kind = passContext.traitDeclarationContext?.traitKind.kind, kind == .external { if case .externalType = type.rawType { - } else { + } else if !allowExternalStructs { // typeAnnotation is describing a Flint type but we are in an external trait declaration diagnostics.append(.flintTypeUsedInExternalTrait(type, at: type.sourceLocation)) } @@ -223,12 +223,14 @@ extension SemanticAnalyzer { } public func process(token: Token, passContext: ASTPassContext) -> ASTPassResult { - var diagnostics = [Diagnostic]() if case .literal(let tokenKind) = token.kind, case .address(let address) = tokenKind, address.count != 42 { - diagnostics.append(.invalidAddressLiteral(token)) + let strip0x = address.index(address.startIndex, offsetBy: 2) + var token = token + token.kind = .literal(.address("0x\(String(repeating: "0", count: 42 - address.count))\(address[strip0x...])")) + return ASTPassResult(element: token, diagnostics: [.invalidAddressLiteral(token)], passContext: passContext) } - return ASTPassResult(element: token, diagnostics: diagnostics, passContext: passContext) + return ASTPassResult(element: token, diagnostics: [], passContext: passContext) } } diff --git a/Sources/SemanticAnalyzer/SemanticAnalyzer.swift b/Sources/SemanticAnalyzer/SemanticAnalyzer.swift index 3071b5fb..551d7008 100644 --- a/Sources/SemanticAnalyzer/SemanticAnalyzer.swift +++ b/Sources/SemanticAnalyzer/SemanticAnalyzer.swift @@ -10,6 +10,8 @@ import Diagnostic /// The `ASTPass` performing semantic analysis. public struct SemanticAnalyzer: ASTPass { + public var allowExternalStructs = false + public init() {} public func postProcess(topLevelModule: TopLevelModule, diff --git a/Sources/SemanticAnalyzer/SemanticError.swift b/Sources/SemanticAnalyzer/SemanticError.swift index 3f4fa30d..90bd3f9d 100644 --- a/Sources/SemanticAnalyzer/SemanticError.swift +++ b/Sources/SemanticAnalyzer/SemanticError.swift @@ -26,7 +26,7 @@ extension Diagnostic { } static func invalidAddressLiteral(_ literalToken: Token) -> Diagnostic { - return Diagnostic(severity: .error, sourceLocation: literalToken.sourceLocation, + return Diagnostic(severity: .warning, sourceLocation: literalToken.sourceLocation, message: "Address literal should be 40 digits long after the prefix") } @@ -87,7 +87,7 @@ extension Diagnostic { } else { // Is fallback return Diagnostic(severity: .note, sourceLocation: candidate.declaration.sourceLocation, - message: "Perhaps you meant this fallback function") + message: "Perhaps you meant this function") } } } @@ -678,7 +678,7 @@ extension Diagnostic { return Diagnostic(severity: .error, sourceLocation: location, // swiftlint:disable line_length - message: "Only Solidity types may be used in external traits. '\(type.name)' is a Flint type", notes: notes) + message: "Only external types may be used in external traits. '\(type.name)' is a Flint type", notes: notes) // swiftlint:enable line_length } @@ -692,7 +692,7 @@ extension Diagnostic { return Diagnostic(severity: .error, sourceLocation: location, // swiftlint:disable line_length - message: "Solidity types may not be used outside of external traits. '\(type.name)' is a Solidity type", notes: notes) + message: "External types may not be used outside of external traits. '\(type.name)' is a external type", notes: notes) // swiftlint:enable line_length } diff --git a/Sources/flint-ca/Analyser.swift b/Sources/flint-ca/Analyser.swift index bd105476..f6c9317e 100644 --- a/Sources/flint-ca/Analyser.swift +++ b/Sources/flint-ca/Analyser.swift @@ -28,9 +28,9 @@ struct Analyser { let diagnosticPool = DiagnosticPool(shouldVerify: false, quiet: false, sourceContext: SourceContext( - sourceFiles: inputFiles, - sourceCodeString: sourceCode, - isForServer: true)) + sourceFiles: inputFiles, + sourceCodeString: sourceCode, + isForServer: true)) let config = CompilerContractAnalyserConfiguration(sourceFiles: inputFiles, sourceCode: sourceCode, diff --git a/Sources/flintc/main.swift b/Sources/flintc/main.swift index 3503a50b..87c09dba 100644 --- a/Sources/flintc/main.swift +++ b/Sources/flintc/main.swift @@ -52,7 +52,7 @@ func main() { let compilationOutcome: CompilationOutcome do { var skipVerifier = skipVerifier - if compilerTarget == .move && !skipVerifier { + if compilerTarget == .move && !skipVerifier && !quiet { warnMoveVerifierSkip() skipVerifier = true } @@ -107,7 +107,7 @@ private func warnMoveVerifierSkip() { print(""" \u{001B}[0;33mWarning: `--target move' is not currently compatible with the verifier \tSkipping verifier - \tTo disable this warning, run with `--skip-verifier'\u{001B}[0;0m + \tTo disable this warning, run with `--skip-verifier' or `--quiet'\u{001B}[0;0m """) } diff --git a/Tests/Integration/SemanticTests/solidity_types.flint b/Tests/Integration/SemanticTests/solidity_types.flint index 65f68b7c..1ac55f59 100644 --- a/Tests/Integration/SemanticTests/solidity_types.flint +++ b/Tests/Integration/SemanticTests/solidity_types.flint @@ -3,21 +3,21 @@ // expected-warning@0 {{No contract declaration in top level module}} struct trait StructureTrait { - init(param: int256) // expected-error {{Solidity types may not be used outside of external traits. 'int256' is a Solidity type}} + init(param: int256) // expected-error {{External types may not be used outside of external traits. 'int256' is a external type}} } struct Structure { func test(param: Int) -> Int {} // expected-error {{Missing return in function expected to return 'Int'}} - func test2(param: string) {} // expected-error {{Solidity types may not be used outside of external traits. 'string' is a Solidity type}} + func test2(param: string) {} // expected-error {{External types may not be used outside of external traits. 'string' is a external type}} - func test3(param: String) -> bool {} // expected-error {{Solidity types may not be used outside of external traits. 'bool' is a Solidity type}} + func test3(param: String) -> bool {} // expected-error {{External types may not be used outside of external traits. 'bool' is a external type}} // expected-error@-1 {{Missing return in function expected to return 'bool'}} - func test4(a: int64, b: int24, c: int256) -> address {} // expected-error {{Solidity types may not be used outside of external traits. 'address' is a Solidity type}} - // expected-error@-1 {{Solidity types may not be used outside of external traits. 'int64' is a Solidity type}} - // expected-error@-2 {{Solidity types may not be used outside of external traits. 'int24' is a Solidity type}} - // expected-error@-3 {{Solidity types may not be used outside of external traits. 'int256' is a Solidity type}} + func test4(a: int64, b: int24, c: int256) -> address {} // expected-error {{External types may not be used outside of external traits. 'address' is a external type}} + // expected-error@-1 {{External types may not be used outside of external traits. 'int64' is a external type}} + // expected-error@-2 {{External types may not be used outside of external traits. 'int24' is a external type}} + // expected-error@-3 {{External types may not be used outside of external traits. 'int256' is a external type}} // expected-error@-4 {{Missing return in function expected to return 'address'}} } @@ -30,9 +30,9 @@ external trait CorrectExternalTrait { } external trait IncorrectExternalTrait { - func test(param: Address, param: String, param: Bool, param: Int) -> Int // expected-error {{Only Solidity types may be used in external traits. 'Int' is a Flint type}} - // expected-error@-1 {{Only Solidity types may be used in external traits. 'Address' is a Flint type}} - // expected-error@-2 {{Only Solidity types may be used in external traits. 'String' is a Flint type}} - // expected-error@-3 {{Only Solidity types may be used in external traits. 'Bool' is a Flint type}} - // expected-error@-4 {{Only Solidity types may be used in external traits. 'Int' is a Flint type}} + func test(param: Address, param: String, param: Bool, param: Int) -> Int // expected-error {{Only external types may be used in external traits. 'Int' is a Flint type}} + // expected-error@-1 {{Only external types may be used in external traits. 'Address' is a Flint type}} + // expected-error@-2 {{Only external types may be used in external traits. 'String' is a Flint type}} + // expected-error@-3 {{Only external types may be used in external traits. 'Bool' is a Flint type}} + // expected-error@-4 {{Only external types may be used in external traits. 'Int' is a Flint type}} } diff --git a/Tests/MoveTests/BehaviourTests/run_behaviour_tests.py b/Tests/MoveTests/BehaviourTests/run_behaviour_tests.py index b6e246de..14b890d8 100755 --- a/Tests/MoveTests/BehaviourTests/run_behaviour_tests.py +++ b/Tests/MoveTests/BehaviourTests/run_behaviour_tests.py @@ -167,7 +167,6 @@ def test(self) -> bool: message or f"Move Missing Error: " f"No error raised in {self.programme.path.name} line {self.expected_fail_line}" ) - print(test.contents()) return False TestFormatter.passed(self.programme.name) diff --git a/Tests/MoveTests/BehaviourTests/tests/externaltraits-counter.flint b/Tests/MoveTests/BehaviourTests/tests/externaltraits-counter.flint index 2bd547bd..d6a397c7 100644 --- a/Tests/MoveTests/BehaviourTests/tests/externaltraits-counter.flint +++ b/Tests/MoveTests/BehaviourTests/tests/externaltraits-counter.flint @@ -1,4 +1,5 @@ -external trait 0x00.BaseCounter { // 0x00 is replaced by `Transaction.` by the testsuite +@module(address: 0x00) // 0x00 is replaced by `Transaction.` by the testsuite +external trait BaseCounter { public func add(n: uint64) public func count() -> uint64 diff --git a/Tests/MoveTests/BehaviourTests/tests/externaltraits-libra.flint b/Tests/MoveTests/BehaviourTests/tests/externaltraits-libra.flint new file mode 100644 index 00000000..948a5c68 --- /dev/null +++ b/Tests/MoveTests/BehaviourTests/tests/externaltraits-libra.flint @@ -0,0 +1,26 @@ +@module(address: 0x00) // 0x00 is replaced by `Transaction.` by the test suite +@resource +@data +external trait Libra { + public func getValue() -> uint64 + public func withdraw(amount: uint64) -> Libra + public func transfer(to: inout Libra) +} + +contract Account { + visible var value: Libra +} + +Account :: sender <- (any) { + public init() { + value = Libra(0x0) + } + + public func balance() -> Int { + return (call! value.getValue()) as! Int + } + + func transfer(to: inout Libra) mutates (value) { + call! value.transfer(to: &to) + } +} diff --git a/Tests/MoveTests/BehaviourTests/tests/externaltraits-libra.mvir b/Tests/MoveTests/BehaviourTests/tests/externaltraits-libra.mvir new file mode 100644 index 00000000..d77231aa --- /dev/null +++ b/Tests/MoveTests/BehaviourTests/tests/externaltraits-libra.mvir @@ -0,0 +1,54 @@ +import Transaction.Account; +import Transaction.Libra; + +main () { + _ = get_txn_sender(); + return; +} + +//! provide module +module Libra { + import 0x0.LibraCoin; + + resource T { + coin: LibraCoin.T + } + + public new(zero: address): Self.T { + if (move(zero) != 0x0) { + assert(false, 9001); + } + return T { + coin: LibraCoin.zero() + }; + } + + public getValue(this: &mut Self.T): u64 { + let coin: &LibraCoin.T; + coin = &move(this).coin; + return LibraCoin.value(move(coin)); + } + + public withdraw(this: &mut Self.T, amount: u64): Self.T { + let coin: &mut LibraCoin.T; + coin = &mut move(this).coin; + return T { + coin: LibraCoin.withdraw(move(coin), move(amount)) + }; + } + + public transfer(this: &mut Self.T, other: &mut Self.T) { + let coin_temp: &LibraCoin.T; + let coin: &mut LibraCoin.T; + let other_coin: &mut LibraCoin.T; + let temporary: LibraCoin.T; + let amount: u64; + coin_temp = ©(this).coin; + amount = LibraCoin.value(move(coin_temp)); + coin = &mut move(this).coin; + temporary = LibraCoin.withdraw(move(coin), move(amount)); + other_coin = &mut move(other).coin; + LibraCoin.deposit(move(other_coin), move(temporary)); + return; + } +} diff --git a/Tests/MoveTests/BehaviourTests/tests/structs.flint b/Tests/MoveTests/BehaviourTests/tests/structs.flint index a1d66505..f8d8ead0 100644 --- a/Tests/MoveTests/BehaviourTests/tests/structs.flint +++ b/Tests/MoveTests/BehaviourTests/tests/structs.flint @@ -38,11 +38,12 @@ contract C { var b: B = B() var c: B = B() let d: Int = 5 - var e: Bool = true + var e: Bool } C :: (any) { public init() { + e = true getE() } diff --git a/docs/compiler_guide.md b/docs/compiler_guide.md index 23bdb397..c390c162 100644 --- a/docs/compiler_guide.md +++ b/docs/compiler_guide.md @@ -27,6 +27,7 @@ The Flint compiler is separated into the following modules, all source files are - [IRGen](#irgen) ouputs the intermediate representation of the program (currently [YUL (formerly IULIA)](https://solidity.readthedocs.io/en/latest/yul.html) assembly code embedded in a Solidity contract). This comprises of: - A preprocessing AST pass that modifies the AST to strip out any convenience syntax and mangle function names so that there are no conflicts. (`Preprocessor/`) - IR generation structures for AST Nodes (`IR*.swift`) which render the IR strings. +- [MoveGen](#movegen) generates the MoveIR representation of the programme. - [Lite](#lite) is the test runner. - [File Check](#file-check) does a comparison of files. Used in [Parser Tests](#parser-tests). - [flintc](#flintc) is the main module that controls the command line interface to pass to the Compiler (`Compiler.swift`) that coordinates all of the modules detailed above to generate the IR. It also preprends the source code of the [Standard Library](#standard-library) to the source code of the inputted files that is passed to the Solidity Compiler (`SolcCompiler.swift`). diff --git a/docs/language_guide.md b/docs/language_guide.md index 50622321..f3713fec 100644 --- a/docs/language_guide.md +++ b/docs/language_guide.md @@ -1,3 +1,4 @@ + # Language Guide :+1::tada: First of all, thank you for the interest in Flint! :tada::+1: @@ -6,6 +7,8 @@ Even though the [Ethereum](https://www.ethereum.org/) platform requires smart co Flint changes that, as a new programming language built for easily writing safe Ethereum smart contracts. Flint is approachable to both experienced and new Ethereum developers, and presents a variety of security features. Much of the language syntax is inspired by [the Swift language](https://swift.org/), making it more approachable than Solidity. +Flint also supports [Libra](https://github.com/libra/libra), Facebook's new block-chain, targeting Move, the IR for programmes on it. This is still somewhat experimental, especially as the blockchain is still only at the test-net stage, however, it does provide a much easier way of writing contracts _(modules)_ on Libra, and is easily extensible as Libra develops. + For a quick start, please have a look at the [Installation](#installation) section first, followed by the [Example](#example) section. # Table of Contents @@ -113,18 +116,23 @@ The first step before using the Flint compiler is to install it. The simplest wa ### Docker -The Flint compiler and its dependencies can be installed using [Docker](https://www.docker.com/): +The Flint compiler and its dependencies can be installed using [Docker](https://www.docker.com/). To run the environment without doing any package installations: ```bash -$ git clone https://github.com/flintlang/flint.git -$ cd flint -$ docker build -t flint . -$ docker run -it flint +git clone --recurse-submodule https://github.com/flintlang/flint.git +cd flint +sudo docker build -t "flint_docker" . +#### ---------------------------------------------- ### +## Docker will build, this process may take some time # +#### ---------------------------------------------- ### +sudo docker run --privileged -i -t flint_docker +## Then, inside the docker container, run +source ~/.bash_profile ``` ### Installing `solc`, the Solidity compiler -A non-Docker Flint install requires the [Solidity](https://github.com/ethereum/solidity) compiler `solc` to be installed. For full installation instructions, see the [Solidity documentation](https://solidity.readthedocs.io/en/latest/installing-solidity.html). +A non-Docker Flint install requires the [Solidity](https://github.com/ethereum/solidity) compiler `solc` (version 0.4.25) to be installed. For full installation instructions, see the [Solidity documentation](https://solidity.readthedocs.io/en/latest/installing-solidity.html). ### Binary packages @@ -133,31 +141,82 @@ Flint is compatible with macOS and Linux platforms, and can be installed by down The latest releases are available on the [GitHub releases page](https://github.com/flintlang/flint/releases). ### Building from source +#### Prerequisites +The following must be installed to build Flint: +* mono 5.20 or later (C# 7.0) +* swiftenv +* clang +* nodejs npm +* swiftlint + +##### Additionally for testing +To run the testing libraries, install: +* truffle 4 + +##### On macOS +* xcode - preferences/Locations/Command Line tools must not be empty (the default) +* homebrew - https://brew.sh, update brew if it isn't new with brew update +* `brew install node` - get node and npm if you don't have them +* `brew install wget` - get wget if you don't have it +* `brew cask install mono-mdk` -The Flint compiler is written in [Swift](https://swift.org/), and requires the Swift compiler to be installed. See the [Swift download page](https://swift.org/download/#releases) for latest releases. +```bash +git clone https://github.com/kylef/swiftenv.git ~/.swiftenv +echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile +echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile +echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile +``` - > For older macOS machines and some Linux distributions it may be easier to use `swiftenv`. See the [`swiftenv` website](https://swiftenv.fuller.li/en/latest/) for installation instructions. +##### On Ubuntu 18.04 LTS +This assumes a standard Ubuntu build with `apt`, `wget`, `curl`, `gnupg`, `ca-certificates` and `git` installed. If you don't have one of them installed, you should be notified during the process. If you have any kind of error, try installing them. Note Ubuntu 16.04 has different installation procedures when using apt and installing Mono, thus amendments will need to be made to this process. +```bash +sudo apt install nodejs npm clang -Once Swift is installed, Flint can be compiled by cloning the GitHub repository and invoking `make`: +## Mono - https://www.mono-project.com/download/stable/ +sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF +echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" \ + | sudo tee /etc/apt/sources.list.d/mono-official-stable.list +sudo apt update +sudo apt install mono-devel -```bash -$ git clone https://github.com/flintlang/flint.git -$ cd flint -$ swiftenv install # only if using swiftenv -$ make +## Swiftenv - https://swiftenv.fuller.li/en/latest/installation.html +git clone https://github.com/kylef/swiftenv.git ~/.swiftenv +echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile +echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile +echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile ``` -The built binary will be available in `.build/debug/flintc`. You can then add `flintc` to your `PATH` using: +#### Installation +In your terminal, run the following commands ```bash -$ export PATH=$PATH:`pwd`/.build/debug/flintc +## Use -jN for multi-core speedup (N >= 2) +git clone --recurse-submodule https://github.com/flintlang/flint.git +cd flint +## No need iff swiftenv has already installed relevent swift version or not using swiftenv +swiftenv install +swift package update +## Create a FLINTPATH for the compiler to run (this may be removed in a future version) +echo "export FLINTPATH=\"$(pwd)\"" >> ~/.bash_profile +source ~/.bash_profile + +make ``` -If you are planning to contribute to the Flint project or wish to run the tests, please also install: +#### Usage +To use Flint to compile a Flint contract (in this example `counter.flint`) into Solidity code, run the following code from inside the Flint project folder: +```bash +export PATH=$FLINTPATH/.build/debug:$PATH +flintc --emit-ir --ir-output ./bin examples/valid/counter.flint +``` - - [Node.js](https://nodejs.org/en/) - - [The Truffle suite](https://truffleframework.com) - for testing contracts and running the integration tests - - [SwiftLint](https://github.com/realm/SwiftLint) - to ensure there are no stylistic or formatting issues with the code +This will generate a main.sol file inside the bin sub-directory which can then be compiled to be deployed on the Etherum block-chain. If you wish to have the output file in the current directory remove bin from the previous command: + +``` +flintc --emit-ir --ir-output ./ examples/valid/counter.flint +``` + +To test it, we recommend using Remix IDE, following [these instructions](https://docs.flintlang.org/docs/language_guide#remix-integration) ## Example @@ -315,7 +374,7 @@ Syntax highlighting in Atom can be obtained by installing the [`language-flint` ## Compilation -Flint compiles Flint source code to [YUL IR](https://solidity.readthedocs.io/en/latest/yul.html) wrapped in Solidity contracts. These can then be compiled into EVM bytecode using the Solidity compiler, and deployed to the Ethereum blockchain using a standard client or the Truffle framework. +Flint compiles Flint source code to [YUL IR](https://solidity.readthedocs.io/en/latest/yul.html) wrapped in Solidity contracts. These can then be compiled into EVM bytecode using the Solidity compiler, and deployed to the Ethereum blockchain using a standard client or the Truffle framework. It also compiles them to MoveIR modules, using the `--target move` flag. A Flint source file named `main.flint` containing a contract `Counter` can be compiled to a Solidity file using: @@ -369,7 +428,7 @@ Comments may be used throughout the source code. Comments are started with a dou Flint is a statically-typed language with a simple type system, with basic support for subtyping through [traits](#traits). > **Planned feature** - > + > > Currently, the types of all constants, variables, function arguments, etc. have to be explicitly declared. Type inference is a planned feature. Flint is a type-safe language. A type safe language encourages clarity about the types of values your code can work with. It performs type checks when compiling code and flags any mismatched types as errors. This enables you to catch and fix errors as early as possible in the development process. @@ -394,6 +453,14 @@ Flint is a type-safe language. A type safe language encourages clarity about the | Polymorphic self | `Self` | See [polymorphic self](#polymorphic-self). | | Structs | | Structs (structures), including [user-defined structs](#structs). | +> **Warning** +> +> As MoveIR doesn't yet support collections, no collection types work when `target --move` is set + +> **Planned feature** +> +> When MoveIR supports collections, arrays and dictionaries will work in Move-targeted code + ### Range types Flint includes two range types, which are shortcuts for expressing ranges of values. These can only be used with `for-in` loops. @@ -417,12 +484,12 @@ for let i: Int in (0...5) { At the moment, both `a` and `b` must be integer literals, not variables! > **Planned feature** - > + > > In the future, it will be possible to iterate up to an arbitrary value. See https://github.com/flintlang/flint/issues/397. -### Solidity types +### External types -When specifying an [external interface](#external-calls), Solidity types must be used. The types usable in Flint are: +When specifying an [external interface](#external-calls), External types must be used. The types usable in Flint are: - `int8`, `int16`, `int24`, ... `int256` (all multiples of 8 bits) - `uint8`, `uint16`, `uint24`, ... `uint256` (all multiples of 8 bits) @@ -431,6 +498,8 @@ When specifying an [external interface](#external-calls), Solidity types must be - `bool` - `bytes32` +> Note that only `bool`, `uint64`, `address` are available in MoveIR due to target restrictions + See [casting](#casting-to-and-from-solidity-types) for more information. ## Constants and variables @@ -601,8 +670,7 @@ Initialisers are special functions called to create a struct or contract instanc The statements that can be used in initialisers are limited to "simple" statements, which means no external calls, control flow statements, etc. After an initialiser is executed, all the state properties of its containing struct or contract should have a value. ### Payable - -(Contract-specific.) +_Only in: Contracts on Solidity_ When a user creates a transaction to call a function, they can attach Wei to send to the contract. Functions which expect Wei to be attached when called must be annotated with the `@payable` annotation, otherwise the transaction will revert when the function is called. @@ -620,8 +688,7 @@ public func receiveMoney(implicit value: Wei) { Payable functions may have an arbitrary amount of parameters, but exactly one needs to be implicit and of a currency type. There may only be one function marked `@payable` in a contract. ### Fallback - -(Contract-specific.) +_Only in: Contracts on Solidity_ Fallback functions are another special kind of function, with a slightly modified declaration syntax: @@ -669,7 +736,7 @@ The declaration of a struct only describes what types of variables it contains, ``` > **Bug** - > + > > Unlike for [function calls](#function-calls), it is not required to write the labels for struct initialiser parameters. Example: @@ -714,7 +781,7 @@ bigRectangle.area() // evaluates to 4000000 by calling the `area` function ``` > **Planned feature** - > + > > In the future a `static` keyword will be added to indicate struct functions which are callable without creating a specific instance. See https://github.com/flintlang/flint/issues/419 ### Structs as function arguments @@ -758,7 +825,7 @@ func byReference(s: inout S) { ``` > **Planned feature** - > + > > Passing structs by value (copying the struct into storage or memory) is a planned feature. See https://github.com/flintlang/flint/issues/133. ## Contracts @@ -871,6 +938,8 @@ Caller groups consist of a list of caller members enclosed in parentheses. These | State property (dictionary of addresses) | `[T: Address]` | The caller address must be contained with in the values of the dictionary. | | Any | `any` | Always. | +> _Move-specific:_ Note that only state properties and `any` work in caller protection blocks due to target limitations. It will be expanded once collections are implemented in the underlying MoveIR + Examples: ```swift @@ -892,7 +961,7 @@ Lottery :: (lucky) { } ``` -The Ethereum address of the caller of a function is unforgeable. It is not possible to impersonate another user, as a consequence of Ethereum’s mechanism which generates public addresses from private keys. Transactions are signed using a private key, and determine the public key of the caller. Stealing a caller capability would hence require stealing a private key. The recommended way for Ethereum users to transfer their ability to call functions is to either change the backing address of the caller capability they have (the smart contract must have a function which allows this), or to securely send their private key to the new owner, outside of the Ethereum platform. +The address of the caller of a function is unforgeable. It is not possible to impersonate another user, as a consequence of both Ethereum’s mechanism which generates public addresses from private keys and MoveIR's transaction system. On Ethereum, transactions are signed using a private key, and determine the public key of the caller. Stealing a caller capability would hence require stealing a private key. The recommended way for Ethereum users to transfer their ability to call functions is to either change the backing address of the caller capability they have (the smart contract must have a function which allows this), or to securely send their private key to the new owner, outside of the Ethereum platform. Calls to Flint functions are validated both at compile-time and runtime, with runtime checks only being added where necessary. @@ -1062,6 +1131,7 @@ public func getName() -> String ``` ### Events +_Only on: Solidity_ JavaScript applications can listen to events emitted by an Ethereum smart contract. @@ -1100,7 +1170,7 @@ Flint has the concept of 'traits', based in part on [traits in the Rust language Contracts or structs can conform to multiple traits. The Flint compiler enforces the implementation of function signatures in the trait and allows usage of the functions declared in them. Traits allow a level of abstraction and code reuse for contracts and structs. > **Planned feature** - > + > > In the future, the Flint standard library will include traits providing common functionality to contracts (`Ownable`, `Burnable`, `MultiSig`, `Pausable`, `ERC20`, `ERC721`, etc.) and structs (`Transferable`, `RawValued`, `Describable` etc.). It will also form the basis for allowing end users to access compiler level guarantees and restrictions as in [assets](#assets) and Numerics. ### Struct traits @@ -1244,9 +1314,10 @@ ToyWallet :: (getOwner) { ### External traits -Traits can be declared for external contracts using the syntax: +External traits allow interfacing with contracts (and resources) from the target language. Traits can be declared for external contracts using the syntax: ```swift + external trait { // trait members } @@ -1311,31 +1382,26 @@ The expressions available in Flint are: | Struct reference | `&` | See [structs as function arguments](#structs-as-function-arguments). | | Function call | `(: , : , ...)` | Call to the function `` with the results of the given expressions `` as parameters. See [function calls](#function-calls). | | Dot access | `.` | Access to the `` field (variable, constant, function) or the result of ``. | -| Index / key access | `[]` | Access to the given key of a list or dictionary. | +| Index / key access | `[]` | Access to the given key of a list or dictionary. _(Only on Solidity)_ | | External call | `call .(: , : , ...)` | Call to the function of an external contract; see [external calls](#external-calls). | | Type cast | ` as! ` | Forced cast of the result of `` to ``; see [casting to and from Solidity types](#casting-to-and-from-solidity-types). | | Attempt | `try? `, `try! ` | Attempt to call a function in a different protection block, see [dynamic checking](#dynamic-checking). | ### Function calls -Functions can then be called from within a contract protection block with the same identifier. The call arguments must be provided in the same order as the one they are declared in (in the function signature), and they must be labeled accordingly (the exception for this is [struct initialisers](#instances)). If any of the optional parameters are not provided, then their default values are going to be used automatically. +Functions can then be called from within a contract protection block with the same identifier. The call arguments must be provided in the same order as the one they are declared in (in the function signature), and they must be labeled accordingly (the exception for this is [struct initialisers](#instances)). If any of the optional parameters are not provided, then their default values are used automatically. ````swift @payable - public func Apply(Name: String, implicit Fee: Wei ) mutates (roster){ - var thisMember: Member - var stake: Int = 0 - - thisMember = Member(Name, caller) - stake = Admission - bankroll(applicant: caller, amount: stake) - ... - } +public func apply(name: String, implicit Fee: Wei ) mutates (balances) { + bankroll(applicant: caller, amount: stake) + ... +} - func bankroll (applicant: Address, amount: Int ) mutates (balances) { - balances [applicant] = amount - } +func bankroll (applicant: Address, amount: Int ) mutates (balances) { + balances[applicant] = amount +} ```` ## Literals @@ -1372,6 +1438,7 @@ Examples: Boolean literals (Flint type `Bool`) are simply `true` and `false`. ### String literals +_Only on: Solidity `--skip-verifier`_ String literals (Flint type `String`) are sets of characters enclosed in double quotes `"..."`. @@ -1384,23 +1451,25 @@ Examples: ``` > **Bug** - > + > > Due to the fact that `Strings` are currently stored in a single EVM memory slot, they cannot be longer than 32 bytes. See https://github.com/flintrocks/flint/issues/133. ### List literals +_Only on: Solidity_ List literals (Flint type `[T]` or `T[n]` for some Flint type `T`) currently only include the empty list `[]`. > **Planned feature** - > + > > In the future, Flint will have non-empty list literals written as `[x, y, z, ...]` where `x`, `y`, `z`, etc. are literals of type `T`. See https://github.com/flintlang/flint/issues/420. ### Dictionary literals +_Only on: Solidity_ Dictionary literals (Flint type `[T: U]` for some Flint types `T` and `U`) currently only include the empty dictionary `[:]`. > **Planned feature** - > + > > In the future, Flint will have non-empty dictionary literals written as `[x: a, y: b, z: c, ...]` where `x`, `y`, `z`, etc. are literals of type `T` and `a`, `b`, `c`, etc. are literals of type `U`. See https://github.com/flintlang/flint/issues/420. ### Self @@ -1440,6 +1509,8 @@ overflowing operators, which will not crash on overflow: - `&-` - Unsafe subtraction - `&*` - Unsafe multiplication +> _Move-specific:_ Due to MoveIR not allowing unsafe operations, unsafe operators are translated into safe operators, so act like `+`, `-`, and `*` + ### Boolean operators These operators all result in `Bool`: @@ -1567,8 +1638,7 @@ if x == 2 { ``` ### Become statements - -(Contract-specific.) +_Only on: Contracts_ The `become` statement can be used to change the type state (see [type states](#type-states)) of the current contract. The execution of code is terminated after a `become` statement is executed, and the contract will then transition to the specified type state. Syntax: @@ -1634,7 +1704,7 @@ However, external contracts include their own set of possible risks and security 2. Interfaces of external contracts may be incorrectly specified – since the EVM does not retain any type information, it is up to the programmer to correctly specify the functions available on an external contract. If the interface is specified incorrectly, this may lead to the wrong function being called and money being lost. > **Planned feature** - > + > > In the future, external calls will include automatic re-entrancy attack protection, where no function of a Flint contract will be callable during the execution of an external call. See https://github.com/flintrocks/flint/issues/74. ### Specifying the interface @@ -1642,12 +1712,13 @@ However, external contracts include their own set of possible risks and security The interface of an external contract is specified using a special `external` trait. Syntax: ```swift + external trait { // functions } ``` -The functions declared inside an external trait may not include any modifiers, and their parameters and return types (if used) must be specified using [Solidity types](#solidity-types). +The functions declared inside an external trait may not include any modifiers, and their parameters and return types (if used) must be specified using [External types](#external-types) on Ethereum. On Move, they may return Move structs. Currently, deploying contracts from within Flint code is not supported, so neither initialisers nor fallbacks can be provided in external traits. @@ -1661,6 +1732,16 @@ external trait ExternalBank { } ``` +#### External Trait Attributes + +External trait attributes are a way of providing compiler directives so Flint can understand how to handle the external traits you've declarared in the produced output. They are used by the target's code generation, and thus are target-specific. Currently, the evm target doesn't have any trait attributes. Move uses the following to allow you to provide enough information about how to handle your external traits: + +```swift +@module(address: 0x0) // Provide the address of the module (the address of the resource is provided as in Ethereum) +@data // The following trait is for a Move struct, not a contract +@resource // Used with @data, the following external struct is a resource +``` + ### Creating an instance To work with an external contract in a type-safe manner, every external trait automatically creates an implicit constructor, which takes a single `address` parameter. @@ -1718,7 +1799,7 @@ X :: (any) { ``` > **Planned feature** - > + > > A third mode will be available in the future, `call?`. It will return an `Optional` type, like in Swift, intended to be used with the (also planned) `if let ...` construct. See https://github.com/flintrocks/flint/issues/140. ### Specifying hyper-parameters @@ -1770,7 +1851,7 @@ X :: (any) { The forced cast (`as!`) expression converts Flint types to Solidity types and vice versa, after performing some basic runtime checks to make sure that the original value fits into the target value, since Solidity supports integer types of smaller sizes than the Flint default of 256 bits. An error results in the transaction being reverted. > **Planned feature** - > + > > In the future, casting failures will be possible to handle using `do-catch` blocks or an optional cast mode `as?`. ## Enumerations @@ -1825,7 +1906,8 @@ enum Numbers: Int { # Standard library -## Assets +## Wei +_Only on: Solidity_ Numerous attacks targeting smart contracts, such as ones relating to re-entrancy calls (see TheDAO), allowed hackers to steal a contract’s Ether. Some of these happened because smart contracts encoded Ether values as integers, making it easy to make mistakes when performing Ether transfers between variables, or to forget to record Ether arriving or leaving the smart contract. @@ -1897,6 +1979,7 @@ Wallet :: (owner) { } ``` +## Assets Wei is an example of an asset, and it is a struct conforming to the `struct trait Asset`, available in the standard library. It is possible to declare custom structs which will behave like assets: ```swift @@ -1961,5 +2044,6 @@ if x == 2 { `fatalError()` is a function exposed that reverts a transaction when called. This means that any contract storage changes are rolledback and no values are returned. ### Send +_Only on: Solidity_ `send(address: Address, value: inout Wei)` sends the `value` Wei to the Ethereum address `address`, and clears the contents of `value`. It is a simpler way to perform a money transfer compared to [external calls](#external-calls), but does not allow hyper-parameters to be specified. diff --git a/examples/move/external_structs.flint b/examples/move/external_structs.flint new file mode 100644 index 00000000..78cd6e97 --- /dev/null +++ b/examples/move/external_structs.flint @@ -0,0 +1,33 @@ +//@module(address: 0x0) // 0x00 is replaced by `Transaction.` by the testsuite +//external trait LibraCoin { +// public func add(n: uint64) +// +// public func count() -> uint64 +// +// public func zero() -> Libra +//} + +@data +@module(address: 0x0) +external trait LibraCoin { + func transfer(from: inout LibraCoin) + + func send(to: address) +} + + +contract Example { + visible var coin: LibraCoin +} + +Example :: sender <- (any) { + public init(value: inout LibraCoin) { + let coin2: LibraCoin = LibraCoin(0x0) + coin = value + } + + public func test() mutates (coin) { + let coin2: LibraCoin = LibraCoin(0x0) + coin2.transfer(from: &coin) + } +} diff --git a/install_deps_and_compile_ubuntu1804.sh b/install_deps_and_compile_ubuntu1804.sh deleted file mode 100644 index afb83787..00000000 --- a/install_deps_and_compile_ubuntu1804.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -apt-get update -apt-get install sudo -sudo apt-get install -y software-properties-common curl git zip sudo wget gnupg ca-certificates apt-transport-https sed python python3 libpython2.7 -sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF -echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list -sudo add-apt-repository -y ppa:ethereum/ethereum -curl -sL https://deb.nodesource.com/setup_11.x | sudo -E bash - -sudo apt-get update -sudo apt-get install -y solc nodejs mono-complete clang -npm install -npm install -g truffle@4 -git clone https://github.com/kylef/swiftenv.git ~/.swiftenv -echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile -echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile -echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile -source ~/.bash_profile && swiftenv install 5.0.2 && swiftenv install 4.2 -git clone https://github.com/realm/SwiftLint.git /tmp/swiftlint -(cd /tmp/swiftlint; swiftenv install; swift build -c release --static-swift-stdlib) -echo 'export PATH="/tmp/swiftlint/.build/x86_64-unknown-linux/release:$PATH"' >> ~/.bash_profile -echo 'export FLINTPATH="$(pwd)"' >> ~/.bash_profile -make -# RUN source ~/.bash_profile after you have run this script \ No newline at end of file diff --git a/stdlib/move/Asset.flint b/stdlib/move/Asset.flint index 054fbd75..7c144ec9 100644 --- a/stdlib/move/Asset.flint +++ b/stdlib/move/Asset.flint @@ -25,46 +25,8 @@ struct trait Asset { } // Returns the funds contained in this asset, as an integer. - func getRawValue() -> Int -} - -// This needs to be removed and made a Flint special type -struct LibraCoin {} - -struct Libra: Asset { - var coin: LibraCoin - - init(unsafeRawValue: Int) { - if unsafeRawValue != 0 { - fatalError() - } - coin = LibraCoin() - } - - // Shouldn't be made available to the Flint's programmer - // Should only be used to initialised Libra received from public contract methods - init(source: inout LibraCoin) { - coin = LibraCoin() - // TO BE IMPLEMENTED (probably using MoveRuntimeFunctions) - } - - init(source: inout Libra, amount: Int) { - coin = LibraCoin() - transfer(source: &source, amount: amount) - } - - init(source: inout Libra) { - coin = LibraCoin() - transfer(source: &source) - } - - func transfer(source: inout Libra, amount: Int) { - // TO BE IMPLEMENTED (probably using MoveRuntimeFunctions) - } + func setRawValue(value: Int) -> Int + // Returns the funds contained in this asset, as an integer. func getRawValue() -> Int - { - // TO BE IMPLEMENTED (probably using MoveRuntimeFunctions) - return 0 - } -} \ No newline at end of file +}