Skip to content

Commit

Permalink
op-chain-ops/script: cleanup and improve console logging (ethereum-op…
Browse files Browse the repository at this point in the history
  • Loading branch information
protolambda authored Aug 28, 2024
1 parent 4797ddb commit 36f093a
Show file tree
Hide file tree
Showing 7 changed files with 640 additions and 394 deletions.
5 changes: 0 additions & 5 deletions op-chain-ops/script/cheatcodes_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,31 +183,26 @@ func (c *CheatCodesPrecompile) Coinbase(addr common.Address) {

// Broadcast_afc98040 implements https://book.getfoundry.sh/cheatcodes/broadcast
func (c *CheatCodesPrecompile) Broadcast_afc98040() error {
c.h.log.Info("broadcasting next call")
return c.h.Prank(nil, nil, false, true)
}

// Broadcast_e6962cdb implements https://book.getfoundry.sh/cheatcodes/broadcast
func (c *CheatCodesPrecompile) Broadcast_e6962cdb(who common.Address) error {
c.h.log.Info("broadcasting next call", "who", who)
return c.h.Prank(&who, nil, false, true)
}

// StartBroadcast_7fb5297f implements https://book.getfoundry.sh/cheatcodes/start-broadcast
func (c *CheatCodesPrecompile) StartBroadcast_7fb5297f() error {
c.h.log.Info("starting repeat-broadcast")
return c.h.Prank(nil, nil, true, true)
}

// StartBroadcast_7fec2a8d implements https://book.getfoundry.sh/cheatcodes/start-broadcast
func (c *CheatCodesPrecompile) StartBroadcast_7fec2a8d(who common.Address) error {
c.h.log.Info("starting repeat-broadcast", "who", who)
return c.h.Prank(&who, nil, true, true)
}

// StopBroadcast implements https://book.getfoundry.sh/cheatcodes/stop-broadcast
func (c *CheatCodesPrecompile) StopBroadcast() error {
c.h.log.Info("stopping repeat-broadcast")
return c.h.StopPrank(true)
}

Expand Down
235 changes: 231 additions & 4 deletions op-chain-ops/script/console.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,247 @@
package script

import (
"bytes"
"fmt"
"math/big"
"reflect"
"strconv"
"strings"
"text/scanner"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
)

//go:generate go run ./consolegen --abi-txt=console2.txt --out=console2_gen.go

type ConsolePrecompile struct {
logger log.Logger
sender func() common.Address
}

func (c *ConsolePrecompile) log(ctx ...any) {
func (c *ConsolePrecompile) log(args ...any) {
sender := c.sender()
logger := c.logger.With("sender", sender)
if len(args) == 0 {
logger.Info("")
return
}
if msg, ok := args[0].(string); ok { // if starting with a string, use it as message. And format with args if needed.
logger.Info(consoleFormat(msg, args[1:]...))
return
} else {
logger.Info(consoleFormat("", args...))
}
}

// Log the sender, since the self-address is always equal to the ConsoleAddr
c.logger.With("sender", sender).Info("console", ctx...)
type stringFormat struct{}
type numberFormat struct{}
type objectFormat struct{}
type integerFormat struct{}
type exponentialFormat struct {
precision int
}
type hexadecimalFormat struct{}

//go:generate go run ./consolegen --abi-txt=console2.txt --out=console2_gen.go
func formatBigInt(x *big.Int, precision int) string {
if precision < 0 {
precision = len(new(big.Int).Abs(x).String()) - 1
return formatBigIntFixedPrecision(x, uint(precision)) + fmt.Sprintf("e%d", precision)
}
return formatBigIntFixedPrecision(x, uint(precision))
}

func formatBigIntFixedPrecision(x *big.Int, precision uint) string {
prec := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(precision)), nil)
integer, remainder := new(big.Int).QuoRem(x, prec, new(big.Int))
if remainder.Sign() != 0 {
decimal := fmt.Sprintf("%0"+fmt.Sprintf("%d", precision)+"d",
new(big.Int).Abs(remainder))
decimal = strings.TrimRight(decimal, "0")
return fmt.Sprintf("%d.%s", integer, decimal)
} else {
return fmt.Sprintf("%d", integer)
}
}

// formatValue formats a value v following the given format-spec.
func formatValue(v any, spec any) string {
switch x := v.(type) {
case string:
switch spec.(type) {
case stringFormat:
return x
case objectFormat:
return fmt.Sprintf("'%s'", v)
default:
return "NaN"
}
case bool:
switch spec.(type) {
case stringFormat:
return fmt.Sprintf("%v", x)
case objectFormat:
return fmt.Sprintf("'%v'", x)
case numberFormat:
if x {
return "1"
}
return "0"
default:
return "NaN"
}
case *big.Int:
switch s := spec.(type) {
case stringFormat, objectFormat, numberFormat, integerFormat:
return fmt.Sprintf("%d", x)
case exponentialFormat:
return formatBigInt(x, s.precision)
case hexadecimalFormat:
return (*hexutil.Big)(x).String()
default:
return fmt.Sprintf("%d", x)
}
case *ABIInt256:
switch s := spec.(type) {
case stringFormat, objectFormat, numberFormat, integerFormat:
return fmt.Sprintf("%d", (*big.Int)(x))
case exponentialFormat:
return formatBigInt((*big.Int)(x), s.precision)
case hexadecimalFormat:
return (*hexutil.Big)(x).String()
default:
return fmt.Sprintf("%d", (*big.Int)(x))
}
case common.Address:
switch spec.(type) {
case stringFormat, hexadecimalFormat:
return x.String()
case objectFormat:
return fmt.Sprintf("'%s'", x)
default:
return "NaN"
}
default:
if typ := reflect.TypeOf(v); (typ.Kind() == reflect.Array || typ.Kind() == reflect.Slice) &&
typ.Elem().Kind() == reflect.Uint8 {
switch spec.(type) {
case stringFormat, hexadecimalFormat:
return fmt.Sprintf("0x%x", v)
case objectFormat:
return fmt.Sprintf("'0x%x'", v)
default:
return "NaN"
}
}
return fmt.Sprintf("%v", v)
}
}

// consoleFormat emulates the foundry-flavor of printf, to format console.log data.
func consoleFormat(fmtMsg string, values ...any) string {
var sc scanner.Scanner
sc.Init(bytes.NewReader([]byte(fmtMsg)))
// default scanner settings are for Go source code parsing. Reset all of that.
sc.Whitespace = 0
sc.Mode = 0
sc.IsIdentRune = func(ch rune, i int) bool {
return false
}

nextValue := func() (v any, ok bool) {
if len(values) > 0 {
v = values[0]
values = values[1:]
return v, true
}
return nil, false
}

// Parses a format-spec from a string sequence (excl. the % prefix)
// Returns the spec (if any), and the consumed characters (to abort with / fall back to)
formatSpecFromChars := func() (spec any, consumed string) {
fmtChar := sc.Scan()
switch fmtChar {
case 's':
return stringFormat{}, "s"
case 'd':
return numberFormat{}, "d"
case 'i':
return integerFormat{}, "i"
case 'o':
return objectFormat{}, "o"
case 'e':
return exponentialFormat{precision: -1}, "e"
case 'x':
return hexadecimalFormat{}, "x"
case scanner.EOF:
return nil, ""
default:
for ; fmtChar != scanner.EOF; fmtChar = sc.Scan() {
if fmtChar == 'e' {
precision, err := strconv.ParseUint(consumed, 10, 16)
consumed += "e"
if err != nil {
return nil, consumed
}
return exponentialFormat{precision: int(precision)}, consumed
}
consumed += string(fmtChar)
if !strings.ContainsRune("0123456789", fmtChar) {
return nil, consumed
}
}
return nil, consumed
}
}

expectFmt := false
var out strings.Builder
for sc.Peek() != scanner.EOF {
if expectFmt {
expectFmt = false
spec, consumed := formatSpecFromChars()
if spec != nil {
value, ok := nextValue()
if ok {
out.WriteString(formatValue(value, spec))
} else {
// rather than panic with an .expect() like foundry,
// just log the original format string
out.WriteRune('%')
out.WriteString(consumed)
}
} else {
// on parser failure, write '%' and consumed characters
out.WriteRune('%')
out.WriteString(consumed)
}
} else {
tok := sc.Scan()
if tok == '%' {
next := sc.Peek()
switch next {
case '%': // %% formats as "%"
out.WriteRune('%')
case scanner.EOF:
out.WriteRune(tok)
default:
expectFmt = true
}
} else {
out.WriteRune(tok)
}
}
}

// for all remaining values, append them to the output
for _, v := range values {
if out.Len() > 0 {
out.WriteRune(' ')
}
out.WriteString(formatValue(v, stringFormat{}))
}
return out.String()
}
Loading

0 comments on commit 36f093a

Please sign in to comment.