From 650911cda3a38db44d2473ac02ee9b81bd7c0057 Mon Sep 17 00:00:00 2001 From: Eduard S Date: Tue, 6 Oct 2020 11:39:15 +0200 Subject: [PATCH] Add methods to get ERC20 constants --- eth/contracts/README.md | 18 ++- eth/contracts/erc20.sol | 19 +++ eth/contracts/erc20/erc20.go | 274 +++++++++++++++++++++++++++++++++++ eth/ethereum.go | 37 +++++ eth/ethereum_test.go | 24 +++ test/ethclient.go | 28 +++- test/ethclient_test.go | 13 ++ 7 files changed, 408 insertions(+), 5 deletions(-) create mode 100644 eth/contracts/erc20.sol create mode 100644 eth/contracts/erc20/erc20.go create mode 100644 eth/ethereum_test.go diff --git a/eth/contracts/README.md b/eth/contracts/README.md index f14c397..023ca1e 100644 --- a/eth/contracts/README.md +++ b/eth/contracts/README.md @@ -11,4 +11,20 @@ You must compile the contracts to get the `.bin` and `.abi` files. The contracts Specifically they have been processed in the commit with hash: `745e8d588496d7762d4084a54bafd4435061ae35` -> abigen version 1.9.21 \ No newline at end of file +> abigen version 1.9.21 + +--- + +ERC20 go code was generated with the following command: +``` +abigen --sol erc20.sol --pkg erc20 --out erc20/erc20.go +``` + +Versions: +``` + $ abigen --version +abigen version 1.9.21-stable-0287d548 + $ solc --version +solc, the solidity compiler commandline interface +Version: 0.7.1+commit.f4a555be.Linux.g++ +``` diff --git a/eth/contracts/erc20.sol b/eth/contracts/erc20.sol new file mode 100644 index 0000000..9e25ef6 --- /dev/null +++ b/eth/contracts/erc20.sol @@ -0,0 +1,19 @@ +pragma solidity ^0.7.0; + +contract ERC20 { + string public constant name = ""; + string public constant symbol = ""; + uint8 public constant decimals = 0; + + /* + function totalSupply() public constant returns (uint); + function balanceOf(address tokenOwner) public constant returns (uint balance); + function allowance(address tokenOwner, address spender) public constant returns (uint remaining); + function transfer(address to, uint tokens) public returns (bool success); + function approve(address spender, uint tokens) public returns (bool success); + function transferFrom(address from, address to, uint tokens) public returns (bool success); + + event Transfer(address indexed from, address indexed to, uint tokens); + event Approval(address indexed tokenOwner, address indexed spender, uint tokens); + */ +} diff --git a/eth/contracts/erc20/erc20.go b/eth/contracts/erc20/erc20.go new file mode 100644 index 0000000..bf67f39 --- /dev/null +++ b/eth/contracts/erc20/erc20.go @@ -0,0 +1,274 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package erc20 + +import ( + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// ERC20ABI is the input ABI used to generate the binding from. +const ERC20ABI = "[{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]" + +// ERC20FuncSigs maps the 4-byte function signature to its string representation. +var ERC20FuncSigs = map[string]string{ + "313ce567": "decimals()", + "06fdde03": "name()", + "95d89b41": "symbol()", +} + +// ERC20Bin is the compiled bytecode used for deploying new contracts. +var ERC20Bin = "0x608060405234801561001057600080fd5b50610123806100206000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c806306fdde03146041578063313ce5671460b957806395d89b41146041575b600080fd5b604760d5565b6040805160208082528351818301528351919283929083019185019080838360005b83811015607f5781810151838201526020016069565b50505050905090810190601f16801560ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60bf60e8565b6040805160ff9092168252519081900360200190f35b6040518060200160405280600081525081565b60008156fea26469706673582212209717f9f3c7b4f090e7741b44c5cb9425a26b593410462c0f4c2c0c0879db648d64736f6c63430007010033" + +// DeployERC20 deploys a new Ethereum contract, binding an instance of ERC20 to it. +func DeployERC20(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ERC20, error) { + parsed, err := abi.JSON(strings.NewReader(ERC20ABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ERC20Bin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ERC20{ERC20Caller: ERC20Caller{contract: contract}, ERC20Transactor: ERC20Transactor{contract: contract}, ERC20Filterer: ERC20Filterer{contract: contract}}, nil +} + +// ERC20 is an auto generated Go binding around an Ethereum contract. +type ERC20 struct { + ERC20Caller // Read-only binding to the contract + ERC20Transactor // Write-only binding to the contract + ERC20Filterer // Log filterer for contract events +} + +// ERC20Caller is an auto generated read-only Go binding around an Ethereum contract. +type ERC20Caller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20Transactor is an auto generated write-only Go binding around an Ethereum contract. +type ERC20Transactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20Filterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ERC20Filterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20Session is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ERC20Session struct { + Contract *ERC20 // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ERC20CallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ERC20CallerSession struct { + Contract *ERC20Caller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ERC20TransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ERC20TransactorSession struct { + Contract *ERC20Transactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ERC20Raw is an auto generated low-level Go binding around an Ethereum contract. +type ERC20Raw struct { + Contract *ERC20 // Generic contract binding to access the raw methods on +} + +// ERC20CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ERC20CallerRaw struct { + Contract *ERC20Caller // Generic read-only contract binding to access the raw methods on +} + +// ERC20TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ERC20TransactorRaw struct { + Contract *ERC20Transactor // Generic write-only contract binding to access the raw methods on +} + +// NewERC20 creates a new instance of ERC20, bound to a specific deployed contract. +func NewERC20(address common.Address, backend bind.ContractBackend) (*ERC20, error) { + contract, err := bindERC20(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ERC20{ERC20Caller: ERC20Caller{contract: contract}, ERC20Transactor: ERC20Transactor{contract: contract}, ERC20Filterer: ERC20Filterer{contract: contract}}, nil +} + +// NewERC20Caller creates a new read-only instance of ERC20, bound to a specific deployed contract. +func NewERC20Caller(address common.Address, caller bind.ContractCaller) (*ERC20Caller, error) { + contract, err := bindERC20(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ERC20Caller{contract: contract}, nil +} + +// NewERC20Transactor creates a new write-only instance of ERC20, bound to a specific deployed contract. +func NewERC20Transactor(address common.Address, transactor bind.ContractTransactor) (*ERC20Transactor, error) { + contract, err := bindERC20(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ERC20Transactor{contract: contract}, nil +} + +// NewERC20Filterer creates a new log filterer instance of ERC20, bound to a specific deployed contract. +func NewERC20Filterer(address common.Address, filterer bind.ContractFilterer) (*ERC20Filterer, error) { + contract, err := bindERC20(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ERC20Filterer{contract: contract}, nil +} + +// bindERC20 binds a generic wrapper to an already deployed contract. +func bindERC20(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ERC20ABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ERC20 *ERC20Raw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _ERC20.Contract.ERC20Caller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ERC20 *ERC20Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC20.Contract.ERC20Transactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC20 *ERC20Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC20.Contract.ERC20Transactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ERC20 *ERC20CallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _ERC20.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ERC20 *ERC20TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC20.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC20 *ERC20TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC20.Contract.contract.Transact(opts, method, params...) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_ERC20 *ERC20Caller) Decimals(opts *bind.CallOpts) (uint8, error) { + var ( + ret0 = new(uint8) + ) + out := ret0 + err := _ERC20.contract.Call(opts, out, "decimals") + return *ret0, err +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_ERC20 *ERC20Session) Decimals() (uint8, error) { + return _ERC20.Contract.Decimals(&_ERC20.CallOpts) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_ERC20 *ERC20CallerSession) Decimals() (uint8, error) { + return _ERC20.Contract.Decimals(&_ERC20.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_ERC20 *ERC20Caller) Name(opts *bind.CallOpts) (string, error) { + var ( + ret0 = new(string) + ) + out := ret0 + err := _ERC20.contract.Call(opts, out, "name") + return *ret0, err +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_ERC20 *ERC20Session) Name() (string, error) { + return _ERC20.Contract.Name(&_ERC20.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_ERC20 *ERC20CallerSession) Name() (string, error) { + return _ERC20.Contract.Name(&_ERC20.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_ERC20 *ERC20Caller) Symbol(opts *bind.CallOpts) (string, error) { + var ( + ret0 = new(string) + ) + out := ret0 + err := _ERC20.contract.Call(opts, out, "symbol") + return *ret0, err +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_ERC20 *ERC20Session) Symbol() (string, error) { + return _ERC20.Contract.Symbol(&_ERC20.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_ERC20 *ERC20CallerSession) Symbol() (string, error) { + return _ERC20.Contract.Symbol(&_ERC20.CallOpts) +} diff --git a/eth/ethereum.go b/eth/ethereum.go index e74393d..cd6788a 100644 --- a/eth/ethereum.go +++ b/eth/ethereum.go @@ -13,9 +13,17 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/hermeznetwork/hermez-node/common" + "github.com/hermeznetwork/hermez-node/eth/contracts/erc20" "github.com/hermeznetwork/hermez-node/log" ) +// ERC20Consts are the constants defined in a particular ERC20 Token instance +type ERC20Consts struct { + Name string + Symbol string + Decimals uint64 +} + // EthereumInterface is the interface to Ethereum type EthereumInterface interface { EthCurrentBlock() (int64, error) @@ -23,6 +31,8 @@ type EthereumInterface interface { EthBlockByNumber(context.Context, int64) (*common.Block, error) EthAddress() (*ethCommon.Address, error) EthTransactionReceipt(context.Context, ethCommon.Hash) (*types.Receipt, error) + + EthERC20Consts(ethCommon.Address) (*ERC20Consts, error) } var ( @@ -264,3 +274,30 @@ func (c *EthereumClient) EthBlockByNumber(ctx context.Context, number int64) (*c } return b, nil } + +// EthERC20Consts returns the constants defined for a particular ERC20 Token instance. +func (c *EthereumClient) EthERC20Consts(tokenAddress ethCommon.Address) (*ERC20Consts, error) { + instance, err := erc20.NewERC20(tokenAddress, c.client) + if err != nil { + return nil, err + } + name, err := instance.Name(nil) + if err != nil { + return nil, err + } + + symbol, err := instance.Symbol(nil) + if err != nil { + return nil, err + } + + decimals, err := instance.Decimals(nil) + if err != nil { + return nil, err + } + return &ERC20Consts{ + Name: name, + Symbol: symbol, + Decimals: uint64(decimals), + }, nil +} diff --git a/eth/ethereum_test.go b/eth/ethereum_test.go new file mode 100644 index 0000000..d9db2ff --- /dev/null +++ b/eth/ethereum_test.go @@ -0,0 +1,24 @@ +package eth + +import ( + "os" + "testing" + + ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEthERC20(t *testing.T) { + address := ethCommon.HexToAddress("0x44021007485550008e0f9f1f7b506c7d970ad8ce") + ethClient, err := ethclient.Dial(os.Getenv("ETHCLIENT_DIAL_URL")) + require.Nil(t, err) + client := NewEthereumClient(ethClient, accountAux, ks, nil) + + consts, err := client.EthERC20Consts(address) + require.Nil(t, err) + assert.Equal(t, "Golem Network Token", consts.Name) + assert.Equal(t, "GNT", consts.Symbol) + assert.Equal(t, uint64(18), consts.Decimals) +} diff --git a/test/ethclient.go b/test/ethclient.go index cb209b8..29d3cba 100644 --- a/test/ethclient.go +++ b/test/ethclient.go @@ -176,6 +176,7 @@ type EthereumBlock struct { Time int64 Hash ethCommon.Hash ParentHash ethCommon.Hash + Tokens map[ethCommon.Address]eth.ERC20Consts // state ethState } @@ -200,14 +201,15 @@ func (b *Block) Next() *Block { blockNext := b.copy() blockNext.Rollup.Events = eth.NewRollupEvents() blockNext.Auction.Events = eth.NewAuctionEvents() - blockNext.Eth = &EthereumBlock{ - BlockNum: b.Eth.BlockNum + 1, - ParentHash: b.Eth.Hash, - } + + blockNext.Eth.BlockNum = b.Eth.BlockNum + 1 + blockNext.Eth.ParentHash = b.Eth.Hash + blockNext.Rollup.Constants = b.Rollup.Constants blockNext.Auction.Constants = b.Auction.Constants blockNext.Rollup.Eth = blockNext.Eth blockNext.Auction.Eth = blockNext.Eth + return blockNext } @@ -360,6 +362,7 @@ func NewClient(l bool, timer Timer, addr *ethCommon.Address, setup *ClientSetup) Time: timer.Time(), Hash: hasher.Next(), ParentHash: ethCommon.Hash{}, + Tokens: make(map[ethCommon.Address]eth.ERC20Consts), }, } blockCurrent.Rollup.Eth = blockCurrent.Eth @@ -508,6 +511,23 @@ func (c *Client) EthTransactionReceipt(ctx context.Context, txHash ethCommon.Has return nil, nil } +// CtlAddERC20 adds an ERC20 token to the blockchain. +func (c *Client) CtlAddERC20(tokenAddr ethCommon.Address, constants eth.ERC20Consts) { + nextBlock := c.nextBlock() + e := nextBlock.Eth + e.Tokens[tokenAddr] = constants +} + +// EthERC20Consts returns the constants defined for a particular ERC20 Token instance. +func (c *Client) EthERC20Consts(tokenAddr ethCommon.Address) (*eth.ERC20Consts, error) { + currentBlock := c.currentBlock() + e := currentBlock.Eth + if constants, ok := e.Tokens[tokenAddr]; ok { + return &constants, nil + } + return nil, fmt.Errorf("tokenAddr not found") +} + // func newHeader(number *big.Int) *types.Header { // return &types.Header{ // Number: number, diff --git a/test/ethclient_test.go b/test/ethclient_test.go index 71d20c3..b899341 100644 --- a/test/ethclient_test.go +++ b/test/ethclient_test.go @@ -62,6 +62,19 @@ func TestClientEth(t *testing.T) { require.Nil(t, err) assert.Equal(t, int64(2), block.EthBlockNum) assert.Equal(t, time.Unix(2, 0), block.Timestamp) + + // Add a token + tokenAddr := ethCommon.HexToAddress("0x44021007485550008e0f9f1f7b506c7d970ad8ce") + constants := eth.ERC20Consts{ + Name: "FooBar", + Symbol: "FOO", + Decimals: 4, + } + c.CtlAddERC20(tokenAddr, constants) + c.CtlMineBlock() + tokenConstants, err := c.EthERC20Consts(tokenAddr) + require.Nil(t, err) + assert.Equal(t, constants, *tokenConstants) } func TestClientAuction(t *testing.T) {