Skip to content

Commit

Permalink
* Added a check for empty bytecodes on contract preparation, indicati…
Browse files Browse the repository at this point in the history
…ng a possible issue with bytecode import

* Significant refactoring of logging to support better record output
* Experimenting with Async transactions at the user level. May lead to `__Async` versions of the primary Eth calls. This would allow groups of transactions to be submitted without waiting for a pending transaction to be mined before the next is sent. This is NOT transaction bundling; that requires a contract.
  • Loading branch information
jmhickman committed May 20, 2022
1 parent aadf370 commit 6b9aa05
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 127 deletions.
28 changes: 20 additions & 8 deletions Contracts.fs
Original file line number Diff line number Diff line change
Expand Up @@ -469,23 +469,34 @@ module ContractFunctions =
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////


///
/// Basic check to ensure there is actually bytecode, in case the file used to provide it was malformed.
let checkForBytecode (bytecode: RawContractBytecode) (abi: ABI) =
let (RawContractBytecode s) = bytecode
match s.Length with
| x when x = 0 -> EmptyBytecodeError |> Error
| _ -> abi |> Ok


///
/// Generates Web3Error if the abi can't be parsed, implying interacting with the contract after deployment will
/// fail. Otherwise, passes along the JsonValue.
///
let private canABIBeParsed abi =
let (ABI _abi) = abi
match JsonValue.TryParse(_abi) with
| Some v -> v |> Ok
| None -> ContractParseFailure "Json was incorrectly formatted or otherwise failed to parse" |> Error
let private canABIBeParsed (pipe: Result<ABI, Web3Error>) =
pipe
|> Result.bind( fun abi ->
let (ABI _abi) = abi
match JsonValue.TryParse(_abi) with
| Some v -> v |> Ok
| None -> ContractParseFailure "Json was incorrectly formatted or otherwise failed to parse" |> Error )


///
/// Adapted version of canABIBeParsed for different pipeline
let private pipeCanABIBeParsed abi (pipe: Result<EthAddress, Web3Error>) =
let private pipeCanABIBeParsed (abi: ABI) (pipe: Result<EthAddress, Web3Error>) =
pipe
|> Result.bind(fun p ->
canABIBeParsed abi
canABIBeParsed (abi |> Ok)// this is kinda stupid but whatever
|> Result.bind(fun b ->
(p, b) |> Ok ))

Expand Down Expand Up @@ -555,7 +566,7 @@ module ContractFunctions =
/// or '0x4' (rinkeby). See `Types.fs` for a set of pre-defined network aliases.
/// * `abi`: An `ABI` associated with the deployed contract.
///
let public loadDeployedContract env address chainId abi =
let public loadDeployedContract env address chainId (abi: ABI) =
address
|> wrapEthAddress
|> pipeCanABIBeParsed abi
Expand Down Expand Up @@ -584,6 +595,7 @@ module ContractFunctions =
///
let public prepareUndeployedContract env bytecode (constructorArguments: EVMDatatype list option) chainId abi =
abi
|> checkForBytecode bytecode
|> canABIBeParsed
|> checkForHashCollisions env
|> buildConstructorHash env
Expand Down
190 changes: 170 additions & 20 deletions Logger.fs
Original file line number Diff line number Diff line change
@@ -1,23 +1,177 @@
namespace web3.fs

open web3.fs

module Logger =

open System

///
/// Just color bindings.
let setColor color =
let internal setColor color =
match color with
| Blue -> Console.ForegroundColor <- ConsoleColor.Blue
| DarkBlue -> Console.ForegroundColor <- ConsoleColor.DarkBlue
| Yellow -> Console.ForegroundColor <- ConsoleColor.Yellow
| Green -> Console.ForegroundColor <- ConsoleColor.Green
| Red -> Console.ForegroundColor <- ConsoleColor.Red
| Gold -> Console.ForegroundColor <- ConsoleColor.DarkYellow


///
/// Revert the color.
let revertColor () =Console.ResetColor()
let internal revertColor () = Console.ResetColor()


let printAndRevert (color: WConsoleColor) message =
setColor color
printf $"{message}"
revertColor ()


///
/// Formatted print of a simple response value, typically an RPC response
let prettySimple (simple: string) =
printAndRevert Blue $"{simple}\n"



///
/// Formatted print of a TransactionReceipt
let prettyTransactionReceipt (receipt: TransactionReceipt) =
let toOrContract =
if receipt.contractAddress.IsSome then
$"Contract deployed to: {receipt.contractAddress.Value}\n"
else $"To: {receipt.toAddr.Value}\n"

let logs =
if receipt.logs.Length > 0 then
receipt.logs
|> List.toArray
|> Array.map(fun s -> "\t" + s)
|> fun a -> String.Join("\n", a)
else ""

if receipt.status = "0x0" then
printAndRevert Red "Failed transaction"
else printAndRevert Green "Successful Transaction\n"
printAndRevert Blue $"Transaction hash: {receipt.transactionHash}\n"
printAndRevert DarkBlue $"From: {receipt.from}\n"
printAndRevert Blue toOrContract
printAndRevert Gold $"Gas used: {receipt.gasUsed |> hexToBigIntP}\n"
printAndRevert Gold $"Effective gas price: {receipt.effectiveGasPrice |> hexToBigIntP}\n"
printAndRevert Gold $"Cumulative gas used: {receipt.cumulativeGasUsed |> hexToBigIntP}\n"
printfn $"Block hash: {receipt.blockHash}"
printfn $"Block number: {receipt.blockNumber}"
printfn $"Index of transaction in block: {receipt.transactionIndex}"
printfn $"Type: {receipt.tType}"
printAndRevert Blue "Logs:\n"
printfn $"{logs}"


///
/// Formatted print of Transaction records
let prettyTransaction (txn: MinedTransaction) =
let accessList =
if txn.accessList.Length > 0 then
txn.accessList
|> List.toArray
|> Array.map(fun s -> "\t" + s)
|> fun a -> String.Join("\n", a)
else ""

printAndRevert Blue $"Transaction hash: {txn.hash}\n"
printAndRevert DarkBlue $"From: {txn.from}\n"
printAndRevert Blue $"To: {txn.toAddr}\n"
printAndRevert Blue "Nonce: "
printfn $"{txn.nonce}"
printAndRevert Blue "Input: "
printfn $"{txn.input}"
printAndRevert Gold $"Value: {txn.value |> hexToBigIntP}\n"
printAndRevert Gold "Gas: "
printfn $"{txn.gas |> hexToBigIntP}"
printAndRevert Gold "Gas price: "
printfn $"{txn.gasPrice |> hexToBigIntP}"
printAndRevert Gold "Max fee per gas: "
printfn $"{txn.maxFeePerGas |> hexToBigIntP}"
printAndRevert Gold "Max priority fee per gas: "
printfn $"{txn.maxPriorityFeePerGas |> hexToBigIntP}"
printfn "Access list: "
printfn $"{accessList}"
printfn $"Block hash: {txn.blockHash}"
printfn $"Block number: {txn.blockNumber |> hexToBigIntP}"
printfn $"Chain ID: {txn.chainId}"
printfn $"R: {txn.r}"
printfn $"S: {txn.s}"
printfn $"V: {txn.v}"
printfn $"Index in block: {txn.transactionIndex}"
printfn $"Transaction type: {txn.tType}"


///
/// Formatted print of a Block record
let prettyBlock (block: EthBlock) =
let sealFields =
if block.sealFields.Length > 0 then
block.sealFields
|> List.toArray
|> Array.map(fun s -> "\t" + s)
|> fun a -> String.Join("\n", a)
else ""

let uncles =
if block.uncles.Length > 0 then
block.uncles
|> List.toArray
|> Array.map(fun s -> "\t" + s)
|> fun a -> String.Join("\n", a)
else ""

let transactions =
if block.transactions.Length > 0 then
block.transactions
|> List.toArray
|> Array.map(fun s -> "\t" + s)
|> fun a -> String.Join("\n", a)
else ""


printAndRevert Blue $"Ethereum Block: {block.number}\n"
printfn $"Block author: {block.author}"
printAndRevert Blue "Miner: "
printfn $"{block.miner}"
printAndRevert Gold "Base fee per gas: "
printfn $"{block.baseFeePerGas |> hexToBigIntP}"
printAndRevert Gold "Gas limit: "
printfn $"{block.gasLimit |> hexToBigIntP}"
printAndRevert Gold "Gas used: "
printfn $"{block.gasUsed |> hexToBigIntP}"
printfn $"Difficulty: {block.difficulty}"
printfn $"Extra data: {block.extraData}"
printfn $"Parent hash: {block.parentHash}"
printfn $"Receipts root: {block.receiptsRoot}"
printfn $"Seal fields: {sealFields}"
printfn $"Uncles hash: {block.sha3Uncles}"
printAndRevert Blue "Block size: "
printfn $"{block.size}"
printf $"Stateroot: {block.stateRoot}\n"
printAndRevert Blue "Timestamp: "
printfn $"{block.timestamp}"
printfn $"Total difficulty: {block.totalDifficulty}"
printfn $"Transactions root: {block.transactionsRoot}"
printfn $"Uncles: {uncles}"
printAndRevert Blue "Transactions:\n"
printfn $"{transactions}"


let prettyCallResult (callResult: EVMDatatype list) =
callResult
|> List.fold (fun acc s ->
let stripped = s.ToString().Replace(QUOTE, EMPTY)
$"{acc}\t{stripped}\n") ""
|> fun p ->
printAndRevert Blue "Call response\n"
printfn $"{p}"

///
/// Emits console messages with color and glyphs based on the incoming message. I'm not certain these locks
Expand All @@ -29,30 +183,26 @@ module Logger =
let! msg = mbox.Receive()
let logType, message = msg
match logType with
| Info ->
lock locker (fun _ ->
setColor Blue
printf "[*] "
revertColor ()
printfn $"{message}")
| Warn ->
lock locker (fun _ ->
setColor Yellow
printf "[-] "
revertColor ()
printfn $"{message}")
| Success ->
lock locker (fun _ ->
setColor Green
printf "[+] "
revertColor ()
printAndRevert Yellow "[-] "
printfn $"{message}")
| Failure ->
lock locker (fun _ ->
setColor Red
printf "[!] "
revertColor ()
printAndRevert Red "[!] "
printfn $"{message}")
| Success ->
lock locker (fun _ ->
printAndRevert Green "[+] Success\n"
match message with
| Block ethBlock -> prettyBlock ethBlock
| Transaction mTransaction -> prettyTransaction mTransaction
| TransactionReceiptResult receipt -> prettyTransactionReceipt receipt
| CallResult evmDatatypes -> prettyCallResult evmDatatypes
| SimpleValue s -> prettySimple s
| Library s -> prettySimple s
| Empty -> ()
| TransactionHash _ -> () )

do! receiver ()
}
Expand Down
34 changes: 4 additions & 30 deletions Logging.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,13 @@ open web3.fs.Types
[<AutoOpen>]
module Logging =


open Logger

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Logging
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////


///
/// Processes CallResponse data to remove quotes. Can't use `trimParameter` because that assumes simple strings
/// with no "words" like "byte" or whatever.
///
let private prepareCallResult callResult =
callResult
|> List.fold (fun acc s ->
let stripped = s.ToString().Replace(QUOTE, EMPTY)
$"{acc}{stripped}\n") ""


///
/// Binds and starts the transaction monitor if a transaction hash was emitted from `makeEthTxn`. Intended to be
/// placed in a transaction pipeline to provide realtime logging of transaction completion.
Expand All @@ -33,30 +22,15 @@ module Logging =
| Error e -> e |> Error


///
/// Handles the emission of information to the console
let private logCallResponse (logger: Logger) callResponse =
match callResponse with
| SimpleValue s -> logger.Post (Success, $"Value: {s}")
| Block ethBlock -> logger.Post (Info, $"Ethereum block:\n{ethBlock}")
| TransactionHash _ -> () // Handled by the monitor
| TransactionReceiptResult rpcTransactionResponse -> logger.Post (Info, $"Transaction receipt:\n{rpcTransactionResponse}")
| Transaction mTransaction -> logger.Post (Info, $"Transaction:\n{mTransaction}")
| CallResult callResult ->
logger.Post (Success, $"Call result:\n{prepareCallResult callResult}")
| Library s -> logger.Post (Info, s)
| Empty -> () // Do nothing


///
/// Forwards CallResponses, logs errors to the console
let private logCallResponsesOrWeb3Errors (logger: Logger) (pipeResult: Result<CallResponses, Web3Error>) =
match pipeResult with
| Ok o -> logCallResponse logger o
| Ok o -> logger.Post (Success, o)
| Error e ->
match e with
| PayableFunctionZeroValueWarning w -> logger.Post (Warn, w)
| e -> logger.Post (Failure, $"{e}")
| PayableFunctionZeroValueWarning w -> logger.Post (Warn, $"{w}" |> Library)
| e -> logger.Post (Failure, $"{e}" |> Library)


///
Expand Down
Loading

0 comments on commit 6b9aa05

Please sign in to comment.