Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions engine/cld/mcms/proposalanalysis/decoder/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,20 @@ func adaptNamedFields(fields []experimentalanalyzer.NamedField) DecodedParameter

params := make(DecodedParameters, len(fields))
for i, field := range fields {
ptype := ""
if field.Value != nil {
ptype := field.TypeName
if ptype == "" && field.Value != nil {
ptype = field.Value.GetType()
}

value := any(field.Value)
if value == nil {
value = field.RawValue
}

params[i] = &decodedParameter{
name: field.Name,
ptype: ptype,
value: field.Value,
value: value,
rawValue: field.RawValue,
}
}
Expand Down
60 changes: 60 additions & 0 deletions engine/cld/mcms/proposalanalysis/format/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package format

import (
"math/big"
"strings"
)

// CommaGroupBigInt adds comma separators to a big.Int for readability.
// E.g: 1000000 -> "1,000,000".
func CommaGroupBigInt(n *big.Int) string {
if n == nil {
return "0"
}

s := n.String()
sign := ""
if strings.HasPrefix(s, "-") {
sign = "-"
s = s[1:]
}

if len(s) <= 3 {
return sign + s
}

var b strings.Builder
b.WriteString(sign)
for i, ch := range s {
if i > 0 && (len(s)-i)%3 == 0 {
b.WriteRune(',')
}
b.WriteRune(ch)
}

return b.String()
}

// FormatTokenAmount converts a raw token amount to a
// human-readable decimal string using the token's decimals.
func FormatTokenAmount(amount *big.Int, decimals uint8) string {
if amount == nil || amount.Sign() == 0 {
return "0"
}

divisor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil)
whole := new(big.Int).Div(amount, divisor)
remainder := new(big.Int).Mod(amount, divisor)

if remainder.Sign() == 0 {
return whole.String()
}

fracStr := remainder.String()
if len(fracStr) < int(decimals) {
fracStr = strings.Repeat("0", int(decimals)-len(fracStr)) + fracStr
}
fracStr = strings.TrimRight(fracStr, "0")

return whole.String() + "." + fracStr
}
80 changes: 80 additions & 0 deletions engine/cld/mcms/proposalanalysis/format/format_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package format

import (
"math/big"
"testing"

"github.com/stretchr/testify/assert"
)

func mustBigInt(s string) *big.Int {
n, ok := new(big.Int).SetString(s, 10)
if !ok {
panic("invalid big.Int: " + s)
}

return n
}

func TestCommaGroupBigInt(t *testing.T) {
t.Parallel()

tests := []struct {
name string
input *big.Int
expected string
}{
{"nil", nil, "0"},
{"zero", big.NewInt(0), "0"},
{"small", big.NewInt(42), "42"},
{"hundreds", big.NewInt(999), "999"},
{"thousands", big.NewInt(1000), "1,000"},
{"millions", big.NewInt(1_000_000), "1,000,000"},
{"wei", new(big.Int).Mul(big.NewInt(25), new(big.Int).Exp(big.NewInt(10), big.NewInt(17), nil)), "2,500,000,000,000,000,000"},
{"negative", big.NewInt(-1234567), "-1,234,567"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tt.expected, CommaGroupBigInt(tt.input))
})
}
}

func TestFormatTokenAmount(t *testing.T) {
t.Parallel()

tests := []struct {
name string
amount *big.Int
decimals uint8
expected string
}{
{"nil", nil, 18, "0"},
{"zero", big.NewInt(0), 18, "0"},
{"6 decimals whole", big.NewInt(1_000_000), 6, "1"},
{"6 decimals fraction", big.NewInt(1_500_000), 6, "1.5"},
{"18 decimals large", new(big.Int).Mul(big.NewInt(1000), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)), 18, "1000"},
{"18 decimals exact fraction", mustBigInt("2500000000000000000"), 18, "2.5"},
{
"exact precision beyond float64",
mustBigInt("123456789012345678"),
18,
"0.123456789012345678",
},
{
"small remainder with leading zeros",
big.NewInt(1_000_001),
6,
"1.000001",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tt.expected, FormatTokenAmount(tt.amount, tt.decimals))
})
}
}
63 changes: 35 additions & 28 deletions engine/cld/mcms/proposalanalysis/renderer/funcmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import (
"encoding/json"
"fmt"
"math/big"
"reflect"
"strconv"
"strings"
"text/template"

chainutils "github.com/smartcontractkit/chainlink-deployments-framework/chain/utils"
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalanalysis/analyzer"
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalanalysis/analyzer/annotation"
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalanalysis/format"
experimentalanalyzer "github.com/smartcontractkit/chainlink-deployments-framework/experimental/analyzer"
)

Expand Down Expand Up @@ -198,50 +200,55 @@ func formatStructField(sf experimentalanalyzer.StructField) string {
return "{ " + strings.Join(parts, ", ") + " }"
}

// commaGrouped adds comma separators to a numeric string for readability.
// commaGrouped adds comma separators to a numeric value for readability.
func commaGrouped(v any) string {
var num *big.Int
if n, ok := v.(json.Number); ok {
v = string(n)
}

switch val := v.(type) {
case *big.Int:
if val == nil {
return nilValue
}
num = val

return format.CommaGroupBigInt(val)
case string:
var ok bool
num, ok = new(big.Int).SetString(val, 10)
num, ok := new(big.Int).SetString(val, 10)
if !ok {
return val
}

return format.CommaGroupBigInt(num)
default:
num = new(big.Int)
if _, err := fmt.Sscan(fmt.Sprintf("%v", v), num); err != nil {
return fmt.Sprintf("%v", v)
rv := reflect.ValueOf(v)
if rv.CanInt() {
return format.CommaGroupBigInt(big.NewInt(rv.Int()))
}
}

s := num.String()
sign := ""
if strings.HasPrefix(s, "-") {
sign = "-"
s = strings.TrimPrefix(s, "-")
}
if len(s) <= 3 {
return sign + s
}
if rv.CanUint() {
return format.CommaGroupBigInt(new(big.Int).SetUint64(rv.Uint()))
}

var b strings.Builder
if sign != "" {
b.WriteString(sign)
}
for i, ch := range s {
if i > 0 && (len(s)-i)%3 == 0 {
b.WriteRune(',')
if rv.CanFloat() {
s := strconv.FormatFloat(rv.Float(), 'f', -1, 64)
parts := strings.Split(s, ".")

num, ok := new(big.Int).SetString(parts[0], 10)
if !ok {
return s
}

intPart := format.CommaGroupBigInt(num)
if len(parts) == 2 {
return intPart + "." + parts[1]
}

return intPart
}
b.WriteRune(ch)
}

return b.String()
return formatValue(v)
}
}

func severitySymbol(severity any) string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ _Annotations:_
{{ range . -}}
{{- if not (isFrameworkAnnotation .Name) -}}
- {{ if .Name }}{{ .Name }}: {{ end }}{{ .Value }}
{{- end -}}
{{ end -}}
{{ end }}{{- end -}}

{{- end -}}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
_Annotations:_
- batch.note: first batch


#### Call 1

- [ ] **OnRamp v1.5.0** `setRateLimiterConfig` ⚠ **warning** 🔴 risk: **high**
Expand All @@ -30,6 +31,7 @@ _Annotations:_
- ccip.lane: ethereum -> arbitrum



#### Call 2

- [ ] **ERC20** `transfer`
Expand Down
7 changes: 5 additions & 2 deletions experimental/analyzer/evm_tx_decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func (p *EVMTxCallDecoder) decodeMethodCall(address string, method *abi.Method,
}
inputs[i] = NamedField{
Name: input.Name,
TypeName: input.Type.String(),
Value: p.decodeArg(input.Name, &input.Type, arg),
RawValue: arg,
}
Expand All @@ -73,6 +74,7 @@ func (p *EVMTxCallDecoder) decodeMethodCall(address string, method *abi.Method,
}
outputs[i] = NamedField{
Name: output.Name,
TypeName: output.Type.String(),
Value: p.decodeArg(output.Name, &output.Type, out),
RawValue: out,
}
Expand Down Expand Up @@ -121,8 +123,9 @@ func (p *EVMTxCallDecoder) decodeStruct(argAbi *abi.Type, argVal any) StructFiel
argFieldTyp := reflect.ValueOf(argVal).FieldByName(argFieldName)
argument := p.decodeArg(argFieldName, argFieldAbi, argFieldTyp.Interface())
fields[i] = NamedField{
Name: argFieldName,
Value: argument,
Name: argFieldName,
TypeName: argFieldAbi.String(),
Value: argument,
}
}

Expand Down
Loading