forked from mirror/go-ethereum
common: move big integer math to common/math (#3699)
* common: remove CurrencyToString Move denomination values to params instead. * common: delete dead code * common: move big integer operations to common/math This commit consolidates all big integer operations into common/math and adds tests and documentation. There should be no change in semantics for BigPow, BigMin, BigMax, S256, U256, Exp and their behaviour is now locked in by tests. The BigD, BytesToBig and Bytes2Big functions don't provide additional value, all uses are replaced by new(big.Int).SetBytes(). BigToBytes is now called PaddedBigBytes, its minimum output size parameter is now specified as the number of bytes instead of bits. The single use of this function is in the EVM's MSTORE instruction. Big and String2Big are replaced by ParseBig, which is slightly stricter. It previously accepted leading zeros for hexadecimal inputs but treated decimal inputs as octal if a leading zero digit was present. ParseUint64 is used in places where String2Big was used to decode a uint64. The new functions MustParseBig and MustParseUint64 are now used in many places where parsing errors were previously ignored. * common: delete unused big integer variables * accounts/abi: replace uses of BytesToBig with use of encoding/binary * common: remove BytesToBig * common: remove Bytes2Big * common: remove BigTrue * cmd/utils: add BigFlag and use it for error-checked integer flags While here, remove environment variable processing for DirectoryFlag because we don't use it. * core: add missing error checks in genesis block parser * common: remove String2Big * cmd/evm: use utils.BigFlag * common/math: check for 256 bit overflow in ParseBig This is supposed to prevent silent overflow/truncation of values in the genesis block JSON. Without this check, a genesis block that set a balance larger than 256 bits would lead to weird behaviour in the VM. * cmd/utils: fixup importrelease/1.6
parent
50ee279f25
commit
5c8fe28b72
@ -1,12 +0,0 @@ |
||||
# See http://help.github.com/ignore-files/ for more about ignoring files. |
||||
# |
||||
# If you find yourself ignoring temporary files generated by your text editor |
||||
# or operating system, you probably want to add a global ignore instead: |
||||
# git config --global core.excludesfile ~/.gitignore_global |
||||
|
||||
/tmp |
||||
*/**/*un~ |
||||
*un~ |
||||
.DS_Store |
||||
*/**/.DS_Store |
||||
|
@ -1,3 +0,0 @@ |
||||
language: go |
||||
go: |
||||
- 1.2 |
@ -1,140 +0,0 @@ |
||||
# common |
||||
|
||||
[![Build |
||||
Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) |
||||
|
||||
The common package contains the ethereum utility library. |
||||
|
||||
# Installation |
||||
|
||||
As a subdirectory the main go-ethereum repository, you get it with |
||||
`go get github.com/ethereum/go-ethereum`. |
||||
|
||||
# Usage |
||||
|
||||
## RLP (Recursive Linear Prefix) Encoding |
||||
|
||||
RLP Encoding is an encoding scheme used by the Ethereum project. It |
||||
encodes any native value or list to a string. |
||||
|
||||
More in depth information about the encoding scheme see the |
||||
[Wiki](http://wiki.ethereum.org/index.php/RLP) article. |
||||
|
||||
```go |
||||
rlp := common.Encode("doge") |
||||
fmt.Printf("%q\n", rlp) // => "\0x83dog" |
||||
|
||||
rlp = common.Encode([]interface{}{"dog", "cat"}) |
||||
fmt.Printf("%q\n", rlp) // => "\0xc8\0x83dog\0x83cat" |
||||
decoded := common.Decode(rlp) |
||||
fmt.Println(decoded) // => ["dog" "cat"] |
||||
``` |
||||
|
||||
## Patricia Trie |
||||
|
||||
Patricie Tree is a merkle trie used by the Ethereum project. |
||||
|
||||
More in depth information about the (modified) Patricia Trie can be |
||||
found on the [Wiki](http://wiki.ethereum.org/index.php/Patricia_Tree). |
||||
|
||||
The patricia trie uses a db as backend and could be anything as long as |
||||
it satisfies the Database interface found in `common/db.go`. |
||||
|
||||
```go |
||||
db := NewDatabase() |
||||
|
||||
// db, root |
||||
trie := common.NewTrie(db, "") |
||||
|
||||
trie.Put("puppy", "dog") |
||||
trie.Put("horse", "stallion") |
||||
trie.Put("do", "verb") |
||||
trie.Put("doge", "coin") |
||||
|
||||
// Look up the key "do" in the trie |
||||
out := trie.Get("do") |
||||
fmt.Println(out) // => verb |
||||
|
||||
trie.Delete("puppy") |
||||
``` |
||||
|
||||
The patricia trie, in combination with RLP, provides a robust, |
||||
cryptographically authenticated data structure that can be used to store |
||||
all (key, value) bindings. |
||||
|
||||
```go |
||||
// ... Create db/trie |
||||
|
||||
// Note that RLP uses interface slices as list |
||||
value := common.Encode([]interface{}{"one", 2, "three", []interface{}{42}}) |
||||
// Store the RLP encoded value of the list |
||||
trie.Put("mykey", value) |
||||
``` |
||||
|
||||
## Value |
||||
|
||||
Value is a Generic Value which is used in combination with RLP data or |
||||
`([])interface{}` structures. It may serve as a bridge between RLP data |
||||
and actual real values and takes care of all the type checking and |
||||
casting. Unlike Go's `reflect.Value` it does not panic if it's unable to |
||||
cast to the requested value. It simple returns the base value of that |
||||
type (e.g. `Slice()` returns []interface{}, `Uint()` return 0, etc). |
||||
|
||||
### Creating a new Value |
||||
|
||||
`NewEmptyValue()` returns a new \*Value with it's initial value set to a |
||||
`[]interface{}` |
||||
|
||||
`AppendList()` appends a list to the current value. |
||||
|
||||
`Append(v)` appends the value (v) to the current value/list. |
||||
|
||||
```go |
||||
val := common.NewEmptyValue().Append(1).Append("2") |
||||
val.AppendList().Append(3) |
||||
``` |
||||
|
||||
### Retrieving values |
||||
|
||||
`Get(i)` returns the `i` item in the list. |
||||
|
||||
`Uint()` returns the value as an unsigned int64. |
||||
|
||||
`Slice()` returns the value as a interface slice. |
||||
|
||||
`Str()` returns the value as a string. |
||||
|
||||
`Bytes()` returns the value as a byte slice. |
||||
|
||||
`Len()` assumes current to be a slice and returns its length. |
||||
|
||||
`Byte()` returns the value as a single byte. |
||||
|
||||
```go |
||||
val := common.NewValue([]interface{}{1,"2",[]interface{}{3}}) |
||||
val.Get(0).Uint() // => 1 |
||||
val.Get(1).Str() // => "2" |
||||
s := val.Get(2) // => Value([]interface{}{3}) |
||||
s.Get(0).Uint() // => 3 |
||||
``` |
||||
|
||||
## Decoding |
||||
|
||||
Decoding streams of RLP data is simplified |
||||
|
||||
```go |
||||
val := common.NewValueFromBytes(rlpData) |
||||
val.Get(0).Uint() |
||||
``` |
||||
|
||||
## Encoding |
||||
|
||||
Encoding from Value to RLP is done with the `Encode` method. The |
||||
underlying value can be anything RLP can encode (int, str, lists, bytes) |
||||
|
||||
```go |
||||
val := common.NewValue([]interface{}{1,"2",[]interface{}{3}}) |
||||
rlp := val.Encode() |
||||
// Store the rlp data |
||||
Store(rlp) |
||||
``` |
@ -1,89 +0,0 @@ |
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package common |
||||
|
||||
import ( |
||||
"bytes" |
||||
"testing" |
||||
) |
||||
|
||||
func TestMisc(t *testing.T) { |
||||
a := Big("10") |
||||
b := Big("57896044618658097711785492504343953926634992332820282019728792003956564819968") |
||||
c := []byte{1, 2, 3, 4} |
||||
z := BitTest(a, 1) |
||||
|
||||
if !z { |
||||
t.Error("Expected true got", z) |
||||
} |
||||
|
||||
U256(a) |
||||
S256(a) |
||||
|
||||
U256(b) |
||||
S256(b) |
||||
|
||||
BigD(c) |
||||
} |
||||
|
||||
func TestBigMax(t *testing.T) { |
||||
a := Big("10") |
||||
b := Big("5") |
||||
|
||||
max1 := BigMax(a, b) |
||||
if max1 != a { |
||||
t.Errorf("Expected %d got %d", a, max1) |
||||
} |
||||
|
||||
max2 := BigMax(b, a) |
||||
if max2 != a { |
||||
t.Errorf("Expected %d got %d", a, max2) |
||||
} |
||||
} |
||||
|
||||
func TestBigMin(t *testing.T) { |
||||
a := Big("10") |
||||
b := Big("5") |
||||
|
||||
min1 := BigMin(a, b) |
||||
if min1 != b { |
||||
t.Errorf("Expected %d got %d", b, min1) |
||||
} |
||||
|
||||
min2 := BigMin(b, a) |
||||
if min2 != b { |
||||
t.Errorf("Expected %d got %d", b, min2) |
||||
} |
||||
} |
||||
|
||||
func TestBigCopy(t *testing.T) { |
||||
a := Big("10") |
||||
b := BigCopy(a) |
||||
c := Big("1000000000000") |
||||
y := BigToBytes(b, 16) |
||||
ybytes := []byte{0, 10} |
||||
z := BigToBytes(c, 16) |
||||
zbytes := []byte{232, 212, 165, 16, 0} |
||||
|
||||
if !bytes.Equal(y, ybytes) { |
||||
t.Error("Got", ybytes) |
||||
} |
||||
|
||||
if !bytes.Equal(z, zbytes) { |
||||
t.Error("Got", zbytes) |
||||
} |
||||
} |
@ -1,190 +0,0 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Spec at https://github.com/ethereum/wiki/wiki/ICAP:-Inter-exchange-Client-Address-Protocol
|
||||
|
||||
package common |
||||
|
||||
import ( |
||||
"errors" |
||||
"math/big" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
var ( |
||||
Base36Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
||||
ICAPLengthError = errors.New("Invalid ICAP length") |
||||
ICAPEncodingError = errors.New("Invalid ICAP encoding") |
||||
ICAPChecksumError = errors.New("Invalid ICAP checksum") |
||||
ICAPCountryCodeError = errors.New("Invalid ICAP country code") |
||||
ICAPAssetIdentError = errors.New("Invalid ICAP asset identifier") |
||||
ICAPInstCodeError = errors.New("Invalid ICAP institution code") |
||||
ICAPClientIdentError = errors.New("Invalid ICAP client identifier") |
||||
) |
||||
|
||||
func ICAPToAddress(s string) (Address, error) { |
||||
switch len(s) { |
||||
case 35: // "XE" + 2 digit checksum + 31 base-36 chars of address
|
||||
return parseICAP(s) |
||||
case 34: // "XE" + 2 digit checksum + 30 base-36 chars of address
|
||||
return parseICAP(s) |
||||
case 20: // "XE" + 2 digit checksum + 3-char asset identifier +
|
||||
// 4-char institution identifier + 9-char institution client identifier
|
||||
return parseIndirectICAP(s) |
||||
default: |
||||
return Address{}, ICAPLengthError |
||||
} |
||||
} |
||||
|
||||
func parseICAP(s string) (Address, error) { |
||||
if !strings.HasPrefix(s, "XE") { |
||||
return Address{}, ICAPCountryCodeError |
||||
} |
||||
if err := validCheckSum(s); err != nil { |
||||
return Address{}, err |
||||
} |
||||
// checksum is ISO13616, Ethereum address is base-36
|
||||
bigAddr, _ := new(big.Int).SetString(s[4:], 36) |
||||
return BigToAddress(bigAddr), nil |
||||
} |
||||
|
||||
func parseIndirectICAP(s string) (Address, error) { |
||||
if !strings.HasPrefix(s, "XE") { |
||||
return Address{}, ICAPCountryCodeError |
||||
} |
||||
if s[4:7] != "ETH" { |
||||
return Address{}, ICAPAssetIdentError |
||||
} |
||||
if err := validCheckSum(s); err != nil { |
||||
return Address{}, err |
||||
} |
||||
// TODO: integrate with ICAP namereg
|
||||
return Address{}, errors.New("not implemented") |
||||
} |
||||
|
||||
func AddressToICAP(a Address) (string, error) { |
||||
enc := base36Encode(a.Big()) |
||||
// zero padd encoded address to Direct ICAP length if needed
|
||||
if len(enc) < 30 { |
||||
enc = join(strings.Repeat("0", 30-len(enc)), enc) |
||||
} |
||||
icap := join("XE", checkDigits(enc), enc) |
||||
return icap, nil |
||||
} |
||||
|
||||
// TODO: integrate with ICAP namereg when it's available
|
||||
func AddressToIndirectICAP(a Address, instCode string) (string, error) { |
||||
// return addressToIndirectICAP(a, instCode)
|
||||
return "", errors.New("not implemented") |
||||
} |
||||
|
||||
func addressToIndirectICAP(a Address, instCode string) (string, error) { |
||||
// TODO: add addressToClientIdent which grabs client ident from ICAP namereg
|
||||
//clientIdent := addressToClientIdent(a)
|
||||
clientIdent := "todo" |
||||
return clientIdentToIndirectICAP(instCode, clientIdent) |
||||
} |
||||
|
||||
func clientIdentToIndirectICAP(instCode, clientIdent string) (string, error) { |
||||
if len(instCode) != 4 || !validBase36(instCode) { |
||||
return "", ICAPInstCodeError |
||||
} |
||||
if len(clientIdent) != 9 || !validBase36(instCode) { |
||||
return "", ICAPClientIdentError |
||||
} |
||||
|
||||
// currently ETH is only valid asset identifier
|
||||
s := join("ETH", instCode, clientIdent) |
||||
return join("XE", checkDigits(s), s), nil |
||||
} |
||||
|
||||
// https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
|
||||
func validCheckSum(s string) error { |
||||
s = join(s[4:], s[:4]) |
||||
expanded, err := iso13616Expand(s) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
checkSumNum, _ := new(big.Int).SetString(expanded, 10) |
||||
if checkSumNum.Mod(checkSumNum, Big97).Cmp(Big1) != 0 { |
||||
return ICAPChecksumError |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func checkDigits(s string) string { |
||||
expanded, _ := iso13616Expand(strings.Join([]string{s, "XE00"}, "")) |
||||
num, _ := new(big.Int).SetString(expanded, 10) |
||||
num.Sub(Big98, num.Mod(num, Big97)) |
||||
|
||||
checkDigits := num.String() |
||||
// zero padd checksum
|
||||
if len(checkDigits) == 1 { |
||||
checkDigits = join("0", checkDigits) |
||||
} |
||||
return checkDigits |
||||
} |
||||
|
||||
// not base-36, but expansion to decimal literal: A = 10, B = 11, ... Z = 35
|
||||
func iso13616Expand(s string) (string, error) { |
||||
var parts []string |
||||
if !validBase36(s) { |
||||
return "", ICAPEncodingError |
||||
} |
||||
for _, c := range s { |
||||
i := uint64(c) |
||||
if i >= 65 { |
||||
parts = append(parts, strconv.FormatUint(uint64(c)-55, 10)) |
||||
} else { |
||||
parts = append(parts, string(c)) |
||||
} |
||||
} |
||||
return join(parts...), nil |
||||
} |
||||
|
||||
func base36Encode(i *big.Int) string { |
||||
var chars []rune |
||||
x := new(big.Int) |
||||
for { |
||||
x.Mod(i, Big36) |
||||
chars = append(chars, rune(Base36Chars[x.Uint64()])) |
||||
i.Div(i, Big36) |
||||
if i.Cmp(Big0) == 0 { |
||||
break |
||||
} |
||||
} |
||||
// reverse slice
|
||||
for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 { |
||||
chars[i], chars[j] = chars[j], chars[i] |
||||
} |
||||
return string(chars) |
||||
} |
||||
|
||||
func validBase36(s string) bool { |
||||
for _, c := range s { |
||||
i := uint64(c) |
||||
// 0-9 or A-Z
|
||||
if i < 48 || (i > 57 && i < 65) || i > 90 { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
func join(s ...string) string { |
||||
return strings.Join(s, "") |
||||
} |
@ -1,91 +0,0 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package common |
||||
|
||||
import "testing" |
||||
|
||||
/* More test vectors: |
||||
https://github.com/ethereum/web3.js/blob/master/test/iban.fromAddress.js
|
||||
https://github.com/ethereum/web3.js/blob/master/test/iban.toAddress.js
|
||||
https://github.com/ethereum/web3.js/blob/master/test/iban.isValid.js
|
||||
https://github.com/ethereum/libethereum/blob/develop/test/libethcore/icap.cpp
|
||||
*/ |
||||
|
||||
type icapTest struct { |
||||
name string |
||||
addr string |
||||
icap string |
||||
} |
||||
|
||||
var icapOKTests = []icapTest{ |
||||
{"Direct1", "0x52dc504a422f0e2a9e7632a34a50f1a82f8224c7", "XE499OG1EH8ZZI0KXC6N83EKGT1BM97P2O7"}, |
||||
{"Direct2", "0x11c5496aee77c1ba1f0854206a26dda82a81d6d8", "XE1222Q908LN1QBBU6XUQSO1OHWJIOS46OO"}, |
||||
{"DirectZeroPrefix", "0x00c5496aee77c1ba1f0854206a26dda82a81d6d8", "XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS"}, |
||||
{"DirectDoubleZeroPrefix", "0x0000a5327eab78357cbf2ae8f3d49fd9d90c7d22", "XE0600DQK33XDTYUCRI0KYM5ELAKXDWWF6"}, |
||||
} |
||||
|
||||
var icapInvalidTests = []icapTest{ |
||||
{"DirectInvalidCheckSum", "", "XE7438O073KYGTWWZN0F2WZ0R8PX5ZPPZS"}, |
||||
{"DirectInvalidCountryCode", "", "XD7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS"}, |
||||
{"DirectInvalidLength36", "", "XE499OG1EH8ZZI0KXC6N83EKGT1BM97P2O77"}, |
||||
{"DirectInvalidLength33", "", "XE499OG1EH8ZZI0KXC6N83EKGT1BM97P2"}, |
||||
|
||||
{"IndirectInvalidCheckSum", "", "XE35ETHXREGGOPHERSSS"}, |
||||
{"IndirectInvalidAssetIdentifier", "", "XE34ETHXREGGOPHERSSS"}, |
||||
{"IndirectInvalidLength19", "", "XE34ETHXREGGOPHERSS"}, |
||||
{"IndirectInvalidLength21", "", "XE34ETHXREGGOPHERSSSS"}, |
||||
} |
||||
|
||||
func TestICAPOK(t *testing.T) { |
||||
for _, test := range icapOKTests { |
||||
decodeEncodeTest(HexToAddress(test.addr), test.icap, t) |
||||
} |
||||
} |
||||
|
||||
func TestICAPInvalid(t *testing.T) { |
||||
for _, test := range icapInvalidTests { |
||||
failedDecodingTest(test.icap, t) |
||||
} |
||||
} |
||||
|
||||
func decodeEncodeTest(addr0 Address, icap0 string, t *testing.T) { |
||||
icap1, err := AddressToICAP(addr0) |
||||
if err != nil { |
||||
t.Errorf("ICAP encoding failed: %s", err) |
||||
} |
||||
if icap1 != icap0 { |
||||
t.Errorf("ICAP mismatch: have: %s want: %s", icap1, icap0) |
||||
} |
||||
|
||||
addr1, err := ICAPToAddress(icap0) |
||||
if err != nil { |
||||
t.Errorf("ICAP decoding failed: %s", err) |
||||
} |
||||
if addr1 != addr0 { |
||||
t.Errorf("Address mismatch: have: %x want: %x", addr1, addr0) |
||||
} |
||||
} |
||||
|
||||
func failedDecodingTest(icap string, t *testing.T) { |
||||
addr, err := ICAPToAddress(icap) |
||||
if err == nil { |
||||
t.Errorf("Expected ICAP decoding to fail.") |
||||
} |
||||
if addr != (Address{}) { |
||||
t.Errorf("Expected empty Address on failed ICAP decoding.") |
||||
} |
||||
} |
@ -1,97 +0,0 @@ |
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package common |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"reflect" |
||||
"sync" |
||||
) |
||||
|
||||
// The list type is an anonymous slice handler which can be used
|
||||
// for containing any slice type to use in an environment which
|
||||
// does not support slice types (e.g., JavaScript, QML)
|
||||
type List struct { |
||||
mut sync.Mutex |
||||
val interface{} |
||||
list reflect.Value |
||||
Length int |
||||
} |
||||
|
||||
// Initialise a new list. Panics if non-slice type is given.
|
||||
func NewList(t interface{}) *List { |
||||
list := reflect.ValueOf(t) |
||||
if list.Kind() != reflect.Slice { |
||||
panic("list container initialized with a non-slice type") |
||||
} |
||||
|
||||
return &List{sync.Mutex{}, t, list, list.Len()} |
||||
} |
||||
|
||||
func EmptyList() *List { |
||||
return NewList([]interface{}{}) |
||||
} |
||||
|
||||
// Get N element from the embedded slice. Returns nil if OOB.
|
||||
func (self *List) Get(i int) interface{} { |
||||
if self.list.Len() > i { |
||||
self.mut.Lock() |
||||
defer self.mut.Unlock() |
||||
|
||||
i := self.list.Index(i).Interface() |
||||
|
||||
return i |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (self *List) GetAsJson(i int) interface{} { |
||||
e := self.Get(i) |
||||
|
||||
r, _ := json.Marshal(e) |
||||
|
||||
return string(r) |
||||
} |
||||
|
||||
// Appends value at the end of the slice. Panics when incompatible value
|
||||
// is given.
|
||||
func (self *List) Append(v interface{}) { |
||||
self.mut.Lock() |
||||
defer self.mut.Unlock() |
||||
|
||||
self.list = reflect.Append(self.list, reflect.ValueOf(v)) |
||||
self.Length = self.list.Len() |
||||
} |
||||
|
||||
// Returns the underlying slice as interface.
|
||||
func (self *List) Interface() interface{} { |
||||
return self.list.Interface() |
||||
} |
||||
|
||||
// For JavaScript <3
|
||||
func (self *List) ToJSON() string { |
||||
// make(T, 0) != nil
|
||||
list := make([]interface{}, 0) |
||||
for i := 0; i < self.Length; i++ { |
||||
list = append(list, self.Get(i)) |
||||
} |
||||
|
||||
data, _ := json.Marshal(list) |
||||
|
||||
return string(data) |
||||
} |
@ -0,0 +1,143 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package math provides integer math utilities.
|
||||
package math |
||||
|
||||
import ( |
||||
"math/big" |
||||
) |
||||
|
||||
var ( |
||||
tt255 = BigPow(2, 255) |
||||
tt256 = BigPow(2, 256) |
||||
tt256m1 = new(big.Int).Sub(tt256, big.NewInt(1)) |
||||
MaxBig256 = new(big.Int).Set(tt256m1) |
||||
) |
||||
|
||||
// ParseBig256 parses s as a 256 bit integer in decimal or hexadecimal syntax.
|
||||
// Leading zeros are accepted. The empty string parses as zero.
|
||||
func ParseBig256(s string) (*big.Int, bool) { |
||||
if s == "" { |
||||
return new(big.Int), true |
||||
} |
||||
var bigint *big.Int |
||||
var ok bool |
||||
if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { |
||||
bigint, ok = new(big.Int).SetString(s[2:], 16) |
||||
} else { |
||||
bigint, ok = new(big.Int).SetString(s, 10) |
||||
} |
||||
if ok && bigint.BitLen() > 256 { |
||||
bigint, ok = nil, false |
||||
} |
||||
return bigint, ok |
||||
} |
||||
|
||||
// MustParseBig parses s as a 256 bit big integer and panics if the string is invalid.
|
||||
func MustParseBig256(s string) *big.Int { |
||||
v, ok := ParseBig256(s) |
||||
if !ok { |
||||
panic("invalid 256 bit integer: " + s) |
||||
} |
||||
return v |
||||
} |
||||
|
||||
// BigPow returns a ** b as a big integer.
|
||||
func BigPow(a, b int64) *big.Int { |
||||
r := big.NewInt(a) |
||||
return r.Exp(r, big.NewInt(b), nil) |
||||
} |
||||
|
||||
// BigMax returns the larger of x or y.
|
||||
func BigMax(x, y *big.Int) *big.Int { |
||||
if x.Cmp(y) < 0 { |
||||
return y |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// BigMin returns the smaller of x or y.
|
||||
func BigMin(x, y *big.Int) *big.Int { |
||||
if x.Cmp(y) > 0 { |
||||
return y |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// FirstBitSet returns the index of the first 1 bit in v, counting from LSB.
|
||||
func FirstBitSet(v *big.Int) int { |
||||
for i := 0; i < v.BitLen(); i++ { |
||||
if v.Bit(i) > 0 { |
||||
return i |
||||
} |
||||
} |
||||
return v.BitLen() |
||||
} |
||||
|
||||
// PaddedBigBytes encodes a big integer as a big-endian byte slice. The length
|
||||
// of the slice is at least n bytes.
|
||||
func PaddedBigBytes(bigint *big.Int, n int) []byte { |
||||
bytes := bigint.Bytes() |
||||
if len(bytes) >= n { |
||||
return bytes |
||||
} |
||||
ret := make([]byte, n) |
||||
return append(ret[:len(ret)-len(bytes)], bytes...) |
||||
} |
||||
|
||||
// U256 encodes as a 256 bit two's complement number. This operation is destructive.
|
||||
func U256(x *big.Int) *big.Int { |
||||
return x.And(x, tt256m1) |
||||
} |
||||
|
||||
// S256 interprets x as a two's complement number.
|
||||
// x must not exceed 256 bits (the result is undefined if it does) and is not modified.
|
||||
//
|
||||
// S256(0) = 0
|
||||
// S256(1) = 1
|
||||
// S256(2**255) = -2**255
|
||||
// S256(2**256-1) = -1
|
||||
func S256(x *big.Int) *big.Int { |
||||
if x.Cmp(tt255) < 0 { |
||||
return x |
||||
} else { |
||||
return new(big.Int).Sub(x, tt256) |
||||
} |
||||
} |
||||
|
||||
// wordSize is the size number of bits in a big.Word.
|
||||
const wordSize = 32 << (uint64(^big.Word(0)) >> 63) |
||||
|
||||
// Exp implements exponentiation by squaring.
|
||||
// Exp returns a newly-allocated big integer and does not change
|
||||
// base or exponent. The result is truncated to 256 bits.
|
||||
//
|
||||
// Courtesy @karalabe and @chfast
|
||||
func Exp(base, exponent *big.Int) *big.Int { |
||||
result := big.NewInt(1) |
||||
|
||||
for _, word := range exponent.Bits() { |
||||
for i := 0; i < wordSize; i++ { |
||||
if word&1 == 1 { |
||||
U256(result.Mul(result, base)) |
||||
} |
||||
U256(base.Mul(base, base)) |
||||
word >>= 1 |
||||
} |
||||
} |
||||
return result |
||||
} |
@ -0,0 +1,196 @@ |
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package math |
||||
|
||||
import ( |
||||
"bytes" |
||||
"math/big" |
||||
"testing" |
||||
) |
||||
|
||||
func TestParseBig256(t *testing.T) { |
||||
tests := []struct { |
||||
input string |
||||
num *big.Int |
||||
ok bool |
||||
}{ |
||||
{"", big.NewInt(0), true}, |
||||
{"0", big.NewInt(0), true}, |
||||
{"0x0", big.NewInt(0), true}, |
||||
{"12345678", big.NewInt(12345678), true}, |
||||
{"0x12345678", big.NewInt(0x12345678), true}, |
||||
{"0X12345678", big.NewInt(0x12345678), true}, |
||||
// Tests for leading zero behaviour:
|
||||
{"0123456789", big.NewInt(123456789), true}, // note: not octal
|
||||
{"00", big.NewInt(0), true}, |
||||
{"0x00", big.NewInt(0), true}, |
||||
{"0x012345678abc", big.NewInt(0x12345678abc), true}, |
||||
// Invalid syntax:
|
||||
{"abcdef", nil, false}, |
||||
{"0xgg", nil, false}, |
||||
// Larger than 256 bits:
|
||||
{"115792089237316195423570985008687907853269984665640564039457584007913129639936", nil, false}, |
||||
} |
||||
for _, test := range tests { |
||||
num, ok := ParseBig256(test.input) |
||||
if ok != test.ok { |
||||
t.Errorf("ParseBig(%q) -> ok = %t, want %t", test.input, ok, test.ok) |
||||
continue |
||||
} |
||||
if num != nil && test.num != nil && num.Cmp(test.num) != 0 { |
||||
t.Errorf("ParseBig(%q) -> %d, want %d", test.input, num, test.num) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestMustParseBig256(t *testing.T) { |
||||
defer func() { |
||||
if recover() == nil { |
||||
t.Error("MustParseBig should've panicked") |
||||
} |
||||
}() |
||||
MustParseBig256("ggg") |
||||
} |
||||
|
||||
func TestBigMax(t *testing.T) { |
||||
a := big.NewInt(10) |
||||
b := big.NewInt(5) |
||||
|
||||
max1 := BigMax(a, b) |
||||
if max1 != a { |
||||
t.Errorf("Expected %d got %d", a, max1) |
||||
} |
||||
|
||||
max2 := BigMax(b, a) |
||||
if max2 != a { |
||||
t.Errorf("Expected %d got %d", a, max2) |
||||
} |
||||
} |
||||
|
||||
func TestBigMin(t *testing.T) { |
||||
a := big.NewInt(10) |
||||
b := big.NewInt(5) |
||||
|
||||
min1 := BigMin(a, b) |
||||
if min1 != b { |
||||
t.Errorf("Expected %d got %d", b, min1) |
||||
} |
||||
|
||||
min2 := BigMin(b, a) |
||||
if min2 != b { |
||||
t.Errorf("Expected %d got %d", b, min2) |
||||
} |
||||
} |
||||
|
||||
func TestFirstBigSet(t *testing.T) { |
||||
tests := []struct { |
||||
num *big.Int |
||||
ix int |
||||
}{ |
||||
{big.NewInt(0), 0}, |
||||
{big.NewInt(1), 0}, |
||||
{big.NewInt(2), 1}, |
||||
{big.NewInt(0x100), 8}, |
||||
} |
||||
for _, test := range tests { |
||||
if ix := FirstBitSet(test.num); ix != test.ix { |
||||
t.Errorf("FirstBitSet(b%b) = %d, want %d", test.num, ix, test.ix) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestPaddedBigBytes(t *testing.T) { |
||||
tests := []struct { |
||||
num *big.Int |
||||
n int |
||||
result []byte |
||||
}{ |
||||
{num: big.NewInt(0), n: 4, result: []byte{0, 0, 0, 0}}, |
||||
{num: big.NewInt(1), n: 4, result: []byte{0, 0, 0, 1}}, |
||||
{num: big.NewInt(512), n: 4, result: []byte{0, 0, 2, 0}}, |
||||
{num: BigPow(2, 32), n: 4, result: []byte{1, 0, 0, 0, 0}}, |
||||
} |
||||
for _, test := range tests { |
||||
if result := PaddedBigBytes(test.num, test.n); !bytes.Equal(result, test.result) { |
||||
t.Errorf("PaddedBigBytes(%d, %d) = %v, want %v", test.num, test.n, result, test.result) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestU256(t *testing.T) { |
||||
tests := []struct{ x, y *big.Int }{ |
||||
{x: big.NewInt(0), y: big.NewInt(0)}, |
||||
{x: big.NewInt(1), y: big.NewInt(1)}, |
||||
{x: BigPow(2, 255), y: BigPow(2, 255)}, |
||||
{x: BigPow(2, 256), y: big.NewInt(0)}, |
||||
{x: new(big.Int).Add(BigPow(2, 256), big.NewInt(1)), y: big.NewInt(1)}, |
||||
// negative values
|
||||
{x: big.NewInt(-1), y: new(big.Int).Sub(BigPow(2, 256), big.NewInt(1))}, |
||||
{x: big.NewInt(-2), y: new(big.Int).Sub(BigPow(2, 256), big.NewInt(2))}, |
||||
{x: BigPow(2, -255), y: big.NewInt(1)}, |
||||
} |
||||
for _, test := range tests { |
||||
if y := U256(new(big.Int).Set(test.x)); y.Cmp(test.y) != 0 { |
||||
t.Errorf("U256(%x) = %x, want %x", test.x, y, test.y) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestS256(t *testing.T) { |
||||
tests := []struct{ x, y *big.Int }{ |
||||
{x: big.NewInt(0), y: big.NewInt(0)}, |
||||
{x: big.NewInt(1), y: big.NewInt(1)}, |
||||
{x: big.NewInt(2), y: big.NewInt(2)}, |
||||
{ |
||||
x: new(big.Int).Sub(BigPow(2, 255), big.NewInt(1)), |
||||
y: new(big.Int).Sub(BigPow(2, 255), big.NewInt(1)), |
||||
}, |
||||
{ |
||||
x: BigPow(2, 255), |
||||
y: new(big.Int).Neg(BigPow(2, 255)), |
||||
}, |
||||
{ |
||||
x: new(big.Int).Sub(BigPow(2, 256), big.NewInt(1)), |
||||
y: big.NewInt(-1), |
||||
}, |
||||
{ |
||||
x: new(big.Int).Sub(BigPow(2, 256), big.NewInt(2)), |
||||
y: big.NewInt(-2), |
||||
}, |
||||
} |
||||
for _, test := range tests { |
||||
if y := S256(test.x); y.Cmp(test.y) != 0 { |
||||
t.Errorf("S256(%x) = %x, want %x", test.x, y, test.y) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestExp(t *testing.T) { |
||||
tests := []struct{ base, exponent, result *big.Int }{ |
||||
{base: big.NewInt(0), exponent: big.NewInt(0), result: big.NewInt(1)}, |
||||
{base: big.NewInt(1), exponent: big.NewInt(0), result: big.NewInt(1)}, |
||||
{base: big.NewInt(1), exponent: big.NewInt(1), result: big.NewInt(1)}, |
||||
{base: big.NewInt(1), exponent: big.NewInt(2), result: big.NewInt(1)}, |
||||
{base: big.NewInt(3), exponent: big.NewInt(144), result: MustParseBig256("507528786056415600719754159741696356908742250191663887263627442114881")}, |
||||
{base: big.NewInt(2), exponent: big.NewInt(255), result: MustParseBig256("57896044618658097711785492504343953926634992332820282019728792003956564819968")}, |
||||
} |
||||
for _, test := range tests { |
||||
if result := Exp(test.base, test.exponent); result.Cmp(test.result) != 0 { |
||||
t.Errorf("Exp(%d, %d) = %d, want %d", test.base, test.exponent, result, test.result) |
||||
} |
||||
} |
||||
} |
@ -1,96 +0,0 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package math |
||||
|
||||
import ( |
||||
"math/big" |
||||
"sort" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
) |
||||
|
||||
type Summer interface { |
||||
Sum(i int) *big.Int |
||||
Len() int |
||||
} |
||||
|
||||
func Sum(slice Summer) (sum *big.Int) { |
||||
sum = new(big.Int) |
||||
|
||||
for i := 0; i < slice.Len(); i++ { |
||||
sum.Add(sum, slice.Sum(i)) |
||||
} |
||||
return |
||||
} |
||||
|
||||
type Vector struct { |
||||
Gas, Price *big.Int |
||||
} |
||||
|
||||
type VectorsBy func(v1, v2 Vector) bool |
||||
|
||||
func (self VectorsBy) Sort(vectors []Vector) { |
||||
bs := vectorSorter{ |
||||
vectors: vectors, |
||||
by: self, |
||||
} |
||||
sort.Sort(bs) |
||||
} |
||||
|
||||
type vectorSorter struct { |
||||
vectors []Vector |
||||
by func(v1, v2 Vector) bool |
||||
} |
||||
|
||||
func (v vectorSorter) Len() int { return len(v.vectors) } |
||||
func (v vectorSorter) Less(i, j int) bool { return v.by(v.vectors[i], v.vectors[j]) } |
||||
func (v vectorSorter) Swap(i, j int) { v.vectors[i], v.vectors[j] = v.vectors[j], v.vectors[i] } |
||||
|
||||
func PriceSort(v1, v2 Vector) bool { return v1.Price.Cmp(v2.Price) < 0 } |
||||
func GasSort(v1, v2 Vector) bool { return v1.Gas.Cmp(v2.Gas) < 0 } |
||||
|
||||
type vectorSummer struct { |
||||
vectors []Vector |
||||
by func(v Vector) *big.Int |
||||
} |
||||
|
||||
type VectorSum func(v Vector) *big.Int |
||||
|
||||
func (v VectorSum) Sum(vectors []Vector) *big.Int { |
||||
vs := vectorSummer{ |
||||
vectors: vectors, |
||||
by: v, |
||||
} |
||||
return Sum(vs) |
||||
} |
||||
|
||||
func (v vectorSummer) Len() int { return len(v.vectors) } |
||||
func (v vectorSummer) Sum(i int) *big.Int { return v.by(v.vectors[i]) } |
||||
|
||||
func GasSum(v Vector) *big.Int { return v.Gas } |
||||
|
||||
var etherInWei = new(big.Rat).SetInt(common.String2Big("1000000000000000000")) |
||||
|
||||
func GasPrice(bp, gl, ep *big.Int) *big.Int { |
||||
BP := new(big.Rat).SetInt(bp) |
||||
GL := new(big.Rat).SetInt(gl) |
||||
EP := new(big.Rat).SetInt(ep) |
||||
GP := new(big.Rat).Quo(BP, GL) |
||||
GP = GP.Quo(GP, EP) |
||||
|
||||
return GP.Mul(GP, etherInWei).Num() |
||||
} |
@ -1,82 +0,0 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package math |
||||
|
||||
import ( |
||||
"fmt" |
||||
"math/big" |
||||
"testing" |
||||
) |
||||
|
||||
type summer struct { |
||||
numbers []*big.Int |
||||
} |
||||
|
||||
func (s summer) Len() int { return len(s.numbers) } |
||||
func (s summer) Sum(i int) *big.Int { |
||||
return s.numbers[i] |
||||
} |
||||
|
||||
func TestSum(t *testing.T) { |
||||
summer := summer{numbers: []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}} |
||||
sum := Sum(summer) |
||||
if sum.Cmp(big.NewInt(6)) != 0 { |
||||
t.Errorf("got sum = %d, want 6", sum) |
||||
} |
||||
} |
||||
|
||||
func TestDist(t *testing.T) { |
||||
var vectors = []Vector{ |
||||
{big.NewInt(1000), big.NewInt(1234)}, |
||||
{big.NewInt(500), big.NewInt(10023)}, |
||||
{big.NewInt(1034), big.NewInt(1987)}, |
||||
{big.NewInt(1034), big.NewInt(1987)}, |
||||
{big.NewInt(8983), big.NewInt(1977)}, |
||||
{big.NewInt(98382), big.NewInt(1887)}, |
||||
{big.NewInt(12398), big.NewInt(1287)}, |
||||
{big.NewInt(12398), big.NewInt(1487)}, |
||||
{big.NewInt(12398), big.NewInt(1987)}, |
||||
{big.NewInt(12398), big.NewInt(128)}, |
||||
{big.NewInt(12398), big.NewInt(1987)}, |
||||
{big.NewInt(1398), big.NewInt(187)}, |
||||
{big.NewInt(12328), big.NewInt(1927)}, |
||||
{big.NewInt(12398), big.NewInt(1987)}, |
||||
{big.NewInt(22398), big.NewInt(1287)}, |
||||
{big.NewInt(1370), big.NewInt(1981)}, |
||||
{big.NewInt(12398), big.NewInt(1957)}, |
||||
{big.NewInt(42198), big.NewInt(1987)}, |
||||
} |
||||
|
||||
VectorsBy(GasSort).Sort(vectors) |
||||
fmt.Println(vectors) |
||||
|
||||
BP := big.NewInt(15) |
||||
GL := big.NewInt(1000000) |
||||
EP := big.NewInt(100) |
||||
fmt.Println("BP", BP, "GL", GL, "EP", EP) |
||||
GP := GasPrice(BP, GL, EP) |
||||
fmt.Println("GP =", GP, "Wei per GU") |
||||
|
||||
S := len(vectors) / 4 |
||||
fmt.Println("L", len(vectors), "S", S) |
||||
for i := 1; i <= S*4; i += S { |
||||
fmt.Printf("T%d = %v\n", i, vectors[i]) |
||||
} |
||||
|
||||
g := VectorSum(GasSum).Sum(vectors) |
||||
fmt.Printf("G = ∑g* (%v)\n", g) |
||||
} |
Loading…
Reference in new issue