diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a5ba49..657c008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ +## 0.0.2 (October 26, 2019) + +NEW FEATURES: + +* Demonstrate Smart Contract integration model + - [x] Compile to ABI + - [x] Generate bindings +* Added support for ERC20 + - [x] Deployed ERC20 implementation - FixedSupplyToken + - [x] Implemented all methods in ERC20Interface + +IMPROVEMENTS: + +* Verified CIS Docker Hardening 1.20 for images where applicable to Dockerfile + - [x] 4.1 Ensure that a user for the container has been created + - [x] 4.2 Ensure that containers use only trusted base images + (HashiCorp Vault/Alpine) + - [x] 4.3 Ensure that unnecessary packages are not installed in the container + - [x] 4.4 Ensure images are scanned and rebuilt to include security patches + (apk update && apk upgrade added to Dockerfile) + - [x] 4.5 - N/A - Ensure Content trust for Docker is Enabled + - [x] 4.6 Ensure that HEALTHCHECK instructions have been added to + container images + - [x] 4.7 Ensure update instructions are not use alone in the Dockerfile + - used epoch date for this in dockerfile/makefile + - [x] 4.8 Ensure setuid and setgid permissions are removed + (vault user prevents this) + - [x] 4.9 Ensure that COPY is used instead of ADD in Dockerfiles + - [x] 4.10 Ensure secrets are not stored in Dockerfiles + - [x] 4.11 Ensure only verified packages are are installed + (using Alpine package manager) +* Smoke Test for transaction signing +* Smoke Test for ERC20 + - [x] Deploy Contract + - [x] Read Token Supply + - [x] Read Token Balance + - [x] Transfer Token + - [x] Approve Transfer + +BUG FIXES: + +* N/A + ## 0.0.1 (October 20, 2019) NEW FEATURES: diff --git a/Dockerfile b/Dockerfile index 1ad33bd..6bc5f59 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,6 @@ # ******** Create a dev environment to build alpine vault plugins and build them ********* # **************************************************************************************** FROM vault:latest as build - # Setup the alpine build environment for golang RUN apk add --update alpine-sdk RUN apk update && apk add go git openssh gcc musl-dev linux-headers @@ -24,6 +23,9 @@ RUN mkdir -p /app/bin \ # ********** This is our actual released container ********** # *********************************************************** FROM vault:latest +# we pass epoch time so it always upgrades +ARG always_upgrade +RUN echo ${always_upgrade} > /dev/null && apk update && apk upgrade USER vault WORKDIR /app RUN mkdir -p /home/vault/ca \ @@ -33,3 +35,4 @@ RUN mkdir -p /home/vault/ca \ # Install the plugin. COPY --from=build /app/bin/immutability-eth-plugin /home/vault/plugins/immutability-eth-plugin COPY --from=build /app/bin/SHA256SUMS /home/vault/plugins/SHA256SUMS +HEALTHCHECK CMD nc -zv 127.0.0.1 8900 || exit 1 diff --git a/contracts/erc20/ERC20Interface.go b/contracts/erc20/ERC20Interface.go new file mode 100644 index 0000000..30ee902 --- /dev/null +++ b/contracts/erc20/ERC20Interface.go @@ -0,0 +1,676 @@ +// 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 + _ = abi.U256 + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// Erc20ABI is the input ABI used to generate the binding from. +const Erc20ABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"spender\",\"type\":\"address\"},{\"name\":\"tokens\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"from\",\"type\":\"address\"},{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"tokens\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"tokenOwner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"balance\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"tokens\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"tokenOwner\",\"type\":\"address\"},{\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"remaining\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"tokens\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"tokenOwner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"tokens\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"}]" + +// 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...) +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address tokenOwner, address spender) constant returns(uint256 remaining) +func (_Erc20 *Erc20Caller) Allowance(opts *bind.CallOpts, tokenOwner common.Address, spender common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _Erc20.contract.Call(opts, out, "allowance", tokenOwner, spender) + return *ret0, err +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address tokenOwner, address spender) constant returns(uint256 remaining) +func (_Erc20 *Erc20Session) Allowance(tokenOwner common.Address, spender common.Address) (*big.Int, error) { + return _Erc20.Contract.Allowance(&_Erc20.CallOpts, tokenOwner, spender) +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address tokenOwner, address spender) constant returns(uint256 remaining) +func (_Erc20 *Erc20CallerSession) Allowance(tokenOwner common.Address, spender common.Address) (*big.Int, error) { + return _Erc20.Contract.Allowance(&_Erc20.CallOpts, tokenOwner, spender) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address tokenOwner) constant returns(uint256 balance) +func (_Erc20 *Erc20Caller) BalanceOf(opts *bind.CallOpts, tokenOwner common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _Erc20.contract.Call(opts, out, "balanceOf", tokenOwner) + return *ret0, err +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address tokenOwner) constant returns(uint256 balance) +func (_Erc20 *Erc20Session) BalanceOf(tokenOwner common.Address) (*big.Int, error) { + return _Erc20.Contract.BalanceOf(&_Erc20.CallOpts, tokenOwner) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address tokenOwner) constant returns(uint256 balance) +func (_Erc20 *Erc20CallerSession) BalanceOf(tokenOwner common.Address) (*big.Int, error) { + return _Erc20.Contract.BalanceOf(&_Erc20.CallOpts, tokenOwner) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() constant 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() constant 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() constant 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() constant 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() constant 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() constant 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() constant 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() constant 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() constant returns(string) +func (_Erc20 *Erc20CallerSession) Symbol() (string, error) { + return _Erc20.Contract.Symbol(&_Erc20.CallOpts) +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() constant returns(uint256) +func (_Erc20 *Erc20Caller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _Erc20.contract.Call(opts, out, "totalSupply") + return *ret0, err +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() constant returns(uint256) +func (_Erc20 *Erc20Session) TotalSupply() (*big.Int, error) { + return _Erc20.Contract.TotalSupply(&_Erc20.CallOpts) +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() constant returns(uint256) +func (_Erc20 *Erc20CallerSession) TotalSupply() (*big.Int, error) { + return _Erc20.Contract.TotalSupply(&_Erc20.CallOpts) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 tokens) returns(bool success) +func (_Erc20 *Erc20Transactor) Approve(opts *bind.TransactOpts, spender common.Address, tokens *big.Int) (*types.Transaction, error) { + return _Erc20.contract.Transact(opts, "approve", spender, tokens) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 tokens) returns(bool success) +func (_Erc20 *Erc20Session) Approve(spender common.Address, tokens *big.Int) (*types.Transaction, error) { + return _Erc20.Contract.Approve(&_Erc20.TransactOpts, spender, tokens) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 tokens) returns(bool success) +func (_Erc20 *Erc20TransactorSession) Approve(spender common.Address, tokens *big.Int) (*types.Transaction, error) { + return _Erc20.Contract.Approve(&_Erc20.TransactOpts, spender, tokens) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 tokens) returns(bool success) +func (_Erc20 *Erc20Transactor) Transfer(opts *bind.TransactOpts, to common.Address, tokens *big.Int) (*types.Transaction, error) { + return _Erc20.contract.Transact(opts, "transfer", to, tokens) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 tokens) returns(bool success) +func (_Erc20 *Erc20Session) Transfer(to common.Address, tokens *big.Int) (*types.Transaction, error) { + return _Erc20.Contract.Transfer(&_Erc20.TransactOpts, to, tokens) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 tokens) returns(bool success) +func (_Erc20 *Erc20TransactorSession) Transfer(to common.Address, tokens *big.Int) (*types.Transaction, error) { + return _Erc20.Contract.Transfer(&_Erc20.TransactOpts, to, tokens) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 tokens) returns(bool success) +func (_Erc20 *Erc20Transactor) TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, tokens *big.Int) (*types.Transaction, error) { + return _Erc20.contract.Transact(opts, "transferFrom", from, to, tokens) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 tokens) returns(bool success) +func (_Erc20 *Erc20Session) TransferFrom(from common.Address, to common.Address, tokens *big.Int) (*types.Transaction, error) { + return _Erc20.Contract.TransferFrom(&_Erc20.TransactOpts, from, to, tokens) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 tokens) returns(bool success) +func (_Erc20 *Erc20TransactorSession) TransferFrom(from common.Address, to common.Address, tokens *big.Int) (*types.Transaction, error) { + return _Erc20.Contract.TransferFrom(&_Erc20.TransactOpts, from, to, tokens) +} + +// Erc20ApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the Erc20 contract. +type Erc20ApprovalIterator struct { + Event *Erc20Approval // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *Erc20ApprovalIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(Erc20Approval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(Erc20Approval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *Erc20ApprovalIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *Erc20ApprovalIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// Erc20Approval represents a Approval event raised by the Erc20 contract. +type Erc20Approval struct { + TokenOwner common.Address + Spender common.Address + Tokens *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed tokenOwner, address indexed spender, uint256 tokens) +func (_Erc20 *Erc20Filterer) FilterApproval(opts *bind.FilterOpts, tokenOwner []common.Address, spender []common.Address) (*Erc20ApprovalIterator, error) { + + var tokenOwnerRule []interface{} + for _, tokenOwnerItem := range tokenOwner { + tokenOwnerRule = append(tokenOwnerRule, tokenOwnerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _Erc20.contract.FilterLogs(opts, "Approval", tokenOwnerRule, spenderRule) + if err != nil { + return nil, err + } + return &Erc20ApprovalIterator{contract: _Erc20.contract, event: "Approval", logs: logs, sub: sub}, nil +} + +// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed tokenOwner, address indexed spender, uint256 tokens) +func (_Erc20 *Erc20Filterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *Erc20Approval, tokenOwner []common.Address, spender []common.Address) (event.Subscription, error) { + + var tokenOwnerRule []interface{} + for _, tokenOwnerItem := range tokenOwner { + tokenOwnerRule = append(tokenOwnerRule, tokenOwnerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _Erc20.contract.WatchLogs(opts, "Approval", tokenOwnerRule, spenderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(Erc20Approval) + if err := _Erc20.contract.UnpackLog(event, "Approval", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// Erc20TransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the Erc20 contract. +type Erc20TransferIterator struct { + Event *Erc20Transfer // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *Erc20TransferIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(Erc20Transfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(Erc20Transfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *Erc20TransferIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *Erc20TransferIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// Erc20Transfer represents a Transfer event raised by the Erc20 contract. +type Erc20Transfer struct { + From common.Address + To common.Address + Tokens *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 tokens) +func (_Erc20 *Erc20Filterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*Erc20TransferIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _Erc20.contract.FilterLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return &Erc20TransferIterator{contract: _Erc20.contract, event: "Transfer", logs: logs, sub: sub}, nil +} + +// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 tokens) +func (_Erc20 *Erc20Filterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *Erc20Transfer, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _Erc20.contract.WatchLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(Erc20Transfer) + if err := _Erc20.contract.UnpackLog(event, "Transfer", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} diff --git a/contracts/erc20/FixedSupplyToken.sol b/contracts/erc20/FixedSupplyToken.sol new file mode 100644 index 0000000..8b7cf66 --- /dev/null +++ b/contracts/erc20/FixedSupplyToken.sol @@ -0,0 +1,228 @@ +pragma solidity ^0.5.0; + +// ---------------------------------------------------------------------------- +// 'FIXED' 'Example Fixed Supply Token' token contract +// +// Symbol : FIXED +// Name : Example Fixed Supply Token +// Total supply: 1,000,000.000000000000000000 +// Decimals : 18 +// +// Enjoy. +// +// (c) BokkyPooBah / Bok Consulting Pty Ltd 2018. The MIT Licence. +// ---------------------------------------------------------------------------- + + +// ---------------------------------------------------------------------------- +// Safe maths +// ---------------------------------------------------------------------------- +library SafeMath { + function add(uint a, uint b) internal pure returns (uint c) { + c = a + b; + require(c >= a); + } + function sub(uint a, uint b) internal pure returns (uint c) { + require(b <= a); + c = a - b; + } + function mul(uint a, uint b) internal pure returns (uint c) { + c = a * b; + require(a == 0 || c / a == b); + } + function div(uint a, uint b) internal pure returns (uint c) { + require(b > 0); + c = a / b; + } +} + + +// ---------------------------------------------------------------------------- +// ERC Token Standard #20 Interface +// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md +// ---------------------------------------------------------------------------- +contract ERC20Interface { + string public constant name = ""; + string public constant symbol = ""; + uint8 public constant decimals = 0; + + function totalSupply() public view returns (uint); + function balanceOf(address tokenOwner) public view returns (uint balance); + function allowance(address tokenOwner, address spender) public view 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); +} + + +// ---------------------------------------------------------------------------- +// Contract function to receive approval and execute function in one call +// +// Borrowed from MiniMeToken +// ---------------------------------------------------------------------------- +contract ApproveAndCallFallBack { + function receiveApproval(address from, uint256 tokens, address token, bytes memory data) public; +} + + +// ---------------------------------------------------------------------------- +// Owned contract +// ---------------------------------------------------------------------------- +contract Owned { + address public owner; + address public newOwner; + + event OwnershipTransferred(address indexed _from, address indexed _to); + + constructor() public { + owner = msg.sender; + } + + modifier onlyOwner { + require(msg.sender == owner); + _; + } + + function transferOwnership(address _newOwner) public onlyOwner { + newOwner = _newOwner; + } + function acceptOwnership() public { + require(msg.sender == newOwner); + emit OwnershipTransferred(owner, newOwner); + owner = newOwner; + newOwner = address(0); + } +} + + +// ---------------------------------------------------------------------------- +// ERC20 Token, with the addition of symbol, name and decimals and a +// fixed supply +// ---------------------------------------------------------------------------- +contract FixedSupplyToken is ERC20Interface, Owned { + using SafeMath for uint; + + string public symbol; + string public name; + uint8 public decimals; + uint _totalSupply; + + mapping(address => uint) balances; + mapping(address => mapping(address => uint)) allowed; + + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + constructor() public { + symbol = "FIXED"; + name = "Example Fixed Supply Token"; + decimals = 18; + _totalSupply = 1000000 * 10**uint(decimals); + balances[owner] = _totalSupply; + emit Transfer(address(0), owner, _totalSupply); + } + + + // ------------------------------------------------------------------------ + // Total supply + // ------------------------------------------------------------------------ + function totalSupply() public view returns (uint) { + return _totalSupply.sub(balances[address(0)]); + } + + + // ------------------------------------------------------------------------ + // Get the token balance for account `tokenOwner` + // ------------------------------------------------------------------------ + function balanceOf(address tokenOwner) public view returns (uint balance) { + return balances[tokenOwner]; + } + + + // ------------------------------------------------------------------------ + // Transfer the balance from token owner's account to `to` account + // - Owner's account must have sufficient balance to transfer + // - 0 value transfers are allowed + // ------------------------------------------------------------------------ + function transfer(address to, uint tokens) public returns (bool success) { + balances[msg.sender] = balances[msg.sender].sub(tokens); + balances[to] = balances[to].add(tokens); + emit Transfer(msg.sender, to, tokens); + return true; + } + + + // ------------------------------------------------------------------------ + // Token owner can approve for `spender` to transferFrom(...) `tokens` + // from the token owner's account + // + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md + // recommends that there are no checks for the approval double-spend attack + // as this should be implemented in user interfaces + // ------------------------------------------------------------------------ + function approve(address spender, uint tokens) public returns (bool success) { + allowed[msg.sender][spender] = tokens; + emit Approval(msg.sender, spender, tokens); + return true; + } + + + // ------------------------------------------------------------------------ + // Transfer `tokens` from the `from` account to the `to` account + // + // The calling account must already have sufficient tokens approve(...)-d + // for spending from the `from` account and + // - From account must have sufficient balance to transfer + // - Spender must have sufficient allowance to transfer + // - 0 value transfers are allowed + // ------------------------------------------------------------------------ + function transferFrom(address from, address to, uint tokens) public returns (bool success) { + balances[from] = balances[from].sub(tokens); + allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens); + balances[to] = balances[to].add(tokens); + emit Transfer(from, to, tokens); + return true; + } + + + // ------------------------------------------------------------------------ + // Returns the amount of tokens approved by the owner that can be + // transferred to the spender's account + // ------------------------------------------------------------------------ + function allowance(address tokenOwner, address spender) public view returns (uint remaining) { + return allowed[tokenOwner][spender]; + } + + + // ------------------------------------------------------------------------ + // Token owner can approve for `spender` to transferFrom(...) `tokens` + // from the token owner's account. The `spender` contract function + // `receiveApproval(...)` is then executed + // ------------------------------------------------------------------------ + function approveAndCall(address spender, uint tokens, bytes memory data) public returns (bool success) { + allowed[msg.sender][spender] = tokens; + emit Approval(msg.sender, spender, tokens); + ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, address(this), data); + return true; + } + + + // ------------------------------------------------------------------------ + // Don't accept ETH + // ------------------------------------------------------------------------ + function () external payable { + revert(); + } + + + // ------------------------------------------------------------------------ + // Owner can transfer out any accidentally sent ERC20 tokens + // ------------------------------------------------------------------------ + function transferAnyERC20Token(address tokenAddress, uint tokens) public onlyOwner returns (bool success) { + return ERC20Interface(tokenAddress).transfer(owner, tokens); + } +} \ No newline at end of file diff --git a/contracts/erc20/build.sh b/contracts/erc20/build.sh new file mode 100755 index 0000000..b6ac91f --- /dev/null +++ b/contracts/erc20/build.sh @@ -0,0 +1,10 @@ +solc --abi --overwrite *.sol -o build +solc --bin --overwrite *.sol -o build +abigen --abi=./build/ERC20Interface.abi --pkg=erc20 --out=ERC20Interface.go +# for filename in ./build/*.abi; do +# BASE=$(basename $filename) +# echo $BASE +# GOFILE=${BASE%.abi} +# echo $GOFILE +# abigen --abi=$filename --pkg=erc20 --out=../$GOFILE.go +# done \ No newline at end of file diff --git a/contracts/erc20/build/ApproveAndCallFallBack.abi b/contracts/erc20/build/ApproveAndCallFallBack.abi new file mode 100644 index 0000000..d6a8a1a --- /dev/null +++ b/contracts/erc20/build/ApproveAndCallFallBack.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"tokens","type":"uint256"},{"name":"token","type":"address"},{"name":"data","type":"bytes"}],"name":"receiveApproval","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/contracts/erc20/build/ApproveAndCallFallBack.bin b/contracts/erc20/build/ApproveAndCallFallBack.bin new file mode 100644 index 0000000..e69de29 diff --git a/contracts/erc20/build/ERC20Interface.abi b/contracts/erc20/build/ERC20Interface.abi new file mode 100644 index 0000000..b18fdb6 --- /dev/null +++ b/contracts/erc20/build/ERC20Interface.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"tokens","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tokenOwner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Approval","type":"event"}] \ No newline at end of file diff --git a/contracts/erc20/build/ERC20Interface.bin b/contracts/erc20/build/ERC20Interface.bin new file mode 100644 index 0000000..e69de29 diff --git a/contracts/erc20/build/FixedSupplyToken.abi b/contracts/erc20/build/FixedSupplyToken.abi new file mode 100644 index 0000000..b2b6790 --- /dev/null +++ b/contracts/erc20/build/FixedSupplyToken.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"tokens","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"acceptOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"tokens","type":"uint256"},{"name":"data","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"newOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"tokenAddress","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transferAnyERC20Token","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tokenOwner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Approval","type":"event"}] \ No newline at end of file diff --git a/contracts/erc20/build/FixedSupplyToken.bin b/contracts/erc20/build/FixedSupplyToken.bin new file mode 100644 index 0000000..074edeb --- /dev/null +++ b/contracts/erc20/build/FixedSupplyToken.bin @@ -0,0 +1 @@ +60806040523480156200001157600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506040518060400160405280600581526020017f4649584544000000000000000000000000000000000000000000000000000000815250600290805190602001906200009f92919062000221565b506040518060400160405280601a81526020017f4578616d706c6520466978656420537570706c7920546f6b656e00000000000081525060039080519060200190620000ed92919062000221565b506012600460006101000a81548160ff021916908360ff160217905550600460009054906101000a900460ff1660ff16600a0a620f424002600581905550600554600660008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6005546040518082815260200191505060405180910390a3620002d0565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200026457805160ff191683800117855562000295565b8280016001018555821562000295579182015b828111156200029457825182559160200191906001019062000277565b5b509050620002a49190620002a8565b5090565b620002cd91905b80821115620002c9576000816000905550600101620002af565b5090565b90565b61157880620002e06000396000f3fe6080604052600436106100e85760003560e01c80638da5cb5b1161008a578063d4ee1d9011610059578063d4ee1d90146105bf578063dc39d06d14610616578063dd62ed3e14610689578063f2fde38b1461070e576100e8565b80638da5cb5b1461035b57806395d89b41146103b2578063a9059cbb14610442578063cae9ca51146104b5576100e8565b806323b872dd116100c657806323b872dd1461021b578063313ce567146102ae57806370a08231146102df57806379ba509714610344576100e8565b806306fdde03146100ed578063095ea7b31461017d57806318160ddd146101f0575b600080fd5b3480156100f957600080fd5b5061010261075f565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610142578082015181840152602081019050610127565b50505050905090810190601f16801561016f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561018957600080fd5b506101d6600480360360408110156101a057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506107fd565b604051808215151515815260200191505060405180910390f35b3480156101fc57600080fd5b506102056108ef565b6040518082815260200191505060405180910390f35b34801561022757600080fd5b506102946004803603606081101561023e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061094a565b604051808215151515815260200191505060405180910390f35b3480156102ba57600080fd5b506102c3610bf5565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102eb57600080fd5b5061032e6004803603602081101561030257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610c08565b6040518082815260200191505060405180910390f35b34801561035057600080fd5b50610359610c51565b005b34801561036757600080fd5b50610370610dee565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3480156103be57600080fd5b506103c7610e13565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156104075780820151818401526020810190506103ec565b50505050905090810190601f1680156104345780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561044e57600080fd5b5061049b6004803603604081101561046557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610eb1565b604051808215151515815260200191505060405180910390f35b3480156104c157600080fd5b506105a5600480360360608110156104d857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561051f57600080fd5b82018360208201111561053157600080fd5b8035906020019184600183028401116401000000008311171561055357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061104c565b604051808215151515815260200191505060405180910390f35b3480156105cb57600080fd5b506105d461127f565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561062257600080fd5b5061066f6004803603604081101561063957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506112a5565b604051808215151515815260200191505060405180910390f35b34801561069557600080fd5b506106f8600480360360408110156106ac57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506113eb565b6040518082815260200191505060405180910390f35b34801561071a57600080fd5b5061075d6004803603602081101561073157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611472565b005b60038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156107f55780601f106107ca576101008083540402835291602001916107f5565b820191906000526020600020905b8154815290600101906020018083116107d857829003601f168201915b505050505081565b600081600760003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000610945600660008073ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205460055461150f90919063ffffffff16565b905090565b600061099e82600660008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461150f90919063ffffffff16565b600660008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610a7082600760008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461150f90919063ffffffff16565b600760008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610b4282600660008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461152990919063ffffffff16565b600660008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b600460009054906101000a900460ff1681565b6000600660008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610cab57600080fd5b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60028054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610ea95780601f10610e7e57610100808354040283529160200191610ea9565b820191906000526020600020905b815481529060010190602001808311610e8c57829003601f168201915b505050505081565b6000610f0582600660003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461150f90919063ffffffff16565b600660003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610f9a82600660008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461152990919063ffffffff16565b600660008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905092915050565b600082600760003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925856040518082815260200191505060405180910390a38373ffffffffffffffffffffffffffffffffffffffff16638f4ffcb1338530866040518563ffffffff1660e01b8152600401808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018481526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561120d5780820151818401526020810190506111f2565b50505050905090810190601f16801561123a5780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b15801561125c57600080fd5b505af1158015611270573d6000803e3d6000fd5b50505050600190509392505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461130057600080fd5b8273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16846040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156113a857600080fd5b505af11580156113bc573d6000803e3d6000fd5b505050506040513d60208110156113d257600080fd5b8101908080519060200190929190505050905092915050565b6000600760008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146114cb57600080fd5b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008282111561151e57600080fd5b818303905092915050565b600081830190508281101561153d57600080fd5b9291505056fea265627a7a723058200926f39699dae5a1f57cb3fbcf7a56a3b06e907ae6e0e6b2d897f543a7dc32c864736f6c634300050a0032 \ No newline at end of file diff --git a/contracts/erc20/build/Owned.abi b/contracts/erc20/build/Owned.abi new file mode 100644 index 0000000..9ae696e --- /dev/null +++ b/contracts/erc20/build/Owned.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[],"name":"acceptOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"newOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"}],"name":"OwnershipTransferred","type":"event"}] \ No newline at end of file diff --git a/contracts/erc20/build/Owned.bin b/contracts/erc20/build/Owned.bin new file mode 100644 index 0000000..2f3dccb --- /dev/null +++ b/contracts/erc20/build/Owned.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506103ed806100606000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806379ba5097146100515780638da5cb5b1461005b578063d4ee1d90146100a5578063f2fde38b146100ef575b600080fd5b610059610133565b005b6100636102d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100ad6102f5565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101316004803603602081101561010557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061031b565b005b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461018d57600080fd5b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461037457600080fd5b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505056fea265627a7a72305820d48bb346f756f1b4c514d0cb6670a26b563bf5d9eb222548a5eb56d86218f01b64736f6c634300050a0032 \ No newline at end of file diff --git a/contracts/erc20/build/SafeMath.abi b/contracts/erc20/build/SafeMath.abi new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/contracts/erc20/build/SafeMath.abi @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/contracts/erc20/build/SafeMath.bin b/contracts/erc20/build/SafeMath.bin new file mode 100644 index 0000000..35699ab --- /dev/null +++ b/contracts/erc20/build/SafeMath.bin @@ -0,0 +1 @@ +60556023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea265627a7a72305820a8567376629c47a3133e19c9c636472d6accc6b72662776af08e7395c3adea9164736f6c634300050a0032 \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 80a1598..de95165 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -30,6 +30,7 @@ services: volumes: - "./ca:/home/vault/ca:rw" - "./config:/home/vault/config:ro" + - "../contracts:/home/vault/contracts:ro" - "../scripts:/home/vault/scripts:ro" entrypoint: > /bin/sh -c " diff --git a/ethereum/backend.go b/ethereum/backend.go index 86aa3de..30df802 100644 --- a/ethereum/backend.go +++ b/ethereum/backend.go @@ -53,6 +53,7 @@ func Backend(conf *logical.BackendConfig) (*PluginBackend, error) { Paths: framework.PathAppend( ConfigPaths(&b), WalletPaths(&b), + ERC20Paths(&b), AccountPaths(&b), ExportPaths(&b), ), diff --git a/ethereum/path_erc20.go b/ethereum/path_erc20.go new file mode 100644 index 0000000..5659194 --- /dev/null +++ b/ethereum/path_erc20.go @@ -0,0 +1,688 @@ +// Copyright (C) Immutability, LLC - All Rights Reserved +// Unauthorized copying of this file, via any medium is strictly prohibited +// Proprietary and confidential +// Written by Jeff Ploughman , August 2019 + +package ethereum + +import ( + "bytes" + "context" + "fmt" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" + "gitlab.com/shearline-gateway/omisego/contracts/erc20" + "gitlab.com/shearline-gateway/omisego/util" +) + +const erc20Contract string = "erc-20" + +// contract ERC20Interface { +// string public constant name = ""; +// string public constant symbol = ""; +// uint8 public constant decimals = 0; + +// function totalSupply() public view returns (uint); +// function balanceOf(address tokenOwner) public view returns (uint balance); +// function allowance(address tokenOwner, address spender) public view 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); +// } + +// ERC20Paths are the path handlers for Ethereum wallets +func ERC20Paths(b *PluginBackend) []*framework.Path { + return []*framework.Path{ + &framework.Path{ + Pattern: ContractPath(erc20Contract, "balanceOf"), + HelpSynopsis: "Return the balance for an address's ERC-20 holdings", + HelpDescription: ` + +Return the balance for an address's ERC-20 holdings. + +`, + Fields: map[string]*framework.FieldSchema{ + "name": &framework.FieldSchema{Type: framework.TypeString}, + "address": &framework.FieldSchema{Type: framework.TypeString}, + "contract": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The address of the ERC-20 token.", + }, + }, + ExistenceCheck: pathExistenceCheck, + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathERC20BalanceOf, + }, + }, + &framework.Path{ + Pattern: ContractPath(erc20Contract, "totalSupply"), + HelpSynopsis: "Return the balance for an address's ERC-20 holdings", + HelpDescription: ` + +Return the total supply for a ERC-20 token. + +`, + Fields: map[string]*framework.FieldSchema{ + "name": &framework.FieldSchema{Type: framework.TypeString}, + "address": &framework.FieldSchema{Type: framework.TypeString}, + "contract": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The address of the ERC-20 token.", + }, + }, + ExistenceCheck: pathExistenceCheck, + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathERC20TotalSupply, + }, + }, + + &framework.Path{ + Pattern: ContractPath(erc20Contract, "transfer"), + HelpSynopsis: "Transfer some ERC-20 holdings to another address", + HelpDescription: ` + +Transfer some ERC-20 holdings to another address. + +`, + Fields: map[string]*framework.FieldSchema{ + "name": &framework.FieldSchema{Type: framework.TypeString}, + "address": &framework.FieldSchema{Type: framework.TypeString}, + "contract": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The address of the ERC-20 token.", + }, + "to": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The address of the wallet to send tokens to.", + }, + "tokens": &framework.FieldSchema{ + Type: framework.TypeString, + Default: "0", + Description: "The number of tokens to transfer.", + }, + }, + ExistenceCheck: pathExistenceCheck, + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.CreateOperation: b.pathERC20Transfer, + logical.UpdateOperation: b.pathERC20Transfer, + }, + }, + &framework.Path{ + Pattern: ContractPath(erc20Contract, "transferFrom"), + HelpSynopsis: "Transfer some ERC-20 holdings from another address to this address", + HelpDescription: ` + +Transfer some ERC-20 holdings from another address to this address. + +`, + Fields: map[string]*framework.FieldSchema{ + "name": &framework.FieldSchema{Type: framework.TypeString}, + "address": &framework.FieldSchema{Type: framework.TypeString}, + "contract": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The address of the ERC-20 token.", + }, + "from": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The address of the wallet to send tokens from.", + }, + "tokens": &framework.FieldSchema{ + Type: framework.TypeString, + Default: "0", + Description: "The number of tokens to transfer.", + }, + }, + ExistenceCheck: pathExistenceCheck, + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.CreateOperation: b.pathERC20TransferFrom, + logical.UpdateOperation: b.pathERC20TransferFrom, + }, + }, + &framework.Path{ + Pattern: ContractPath(erc20Contract, "approve"), + HelpSynopsis: "Allow spender to withdraw from your account", + HelpDescription: ` + +Allow spender to withdraw from your account, multiple times, up to the tokens amount. +If this function is called again it overwrites the current allowance with _value. + +`, + Fields: map[string]*framework.FieldSchema{ + "name": &framework.FieldSchema{Type: framework.TypeString}, + "address": &framework.FieldSchema{Type: framework.TypeString}, + "contract": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The address of the ERC-20 token.", + }, + "spender": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The address of the spender.", + }, + "tokens": &framework.FieldSchema{ + Type: framework.TypeString, + Default: "0", + Description: "The number of tokens to transfer.", + }, + }, + ExistenceCheck: pathExistenceCheck, + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.CreateOperation: b.pathERC20Approve, + logical.UpdateOperation: b.pathERC20Approve, + }, + }, + } +} + +func (b *PluginBackend) pathERC20BalanceOf(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + config, err := b.configured(ctx, req) + if err != nil { + return nil, err + } + ownerAddress := common.HexToAddress(data.Get("address").(string)) + + client, err := ethclient.Dial(config.getRPCURL()) + if err != nil { + return nil, err + } + + contractAddress := common.HexToAddress(data.Get("contract").(string)) + instance, err := erc20.NewErc20(contractAddress, client) + if err != nil { + return nil, err + } + callOpts := &bind.CallOpts{} + erc20CallerSession := &erc20.Erc20CallerSession{ + Contract: &instance.Erc20Caller, // Generic contract caller binding to set the session for + CallOpts: *callOpts, // Call options to use throughout this session + } + + bal, err := erc20CallerSession.BalanceOf(ownerAddress) + if err != nil { + return nil, err + } + + tokenName, err := erc20CallerSession.Name() + if err != nil { + return nil, err + } + + symbol, err := erc20CallerSession.Symbol() + if err != nil { + return nil, err + } + + decimals, err := erc20CallerSession.Decimals() + if err != nil { + return nil, err + } + + fbal := new(big.Float) + fbal.SetString(bal.String()) + value := new(big.Float).Quo(fbal, big.NewFloat(math.Pow10(int(decimals)))) + + return &logical.Response{ + Data: map[string]interface{}{ + "contract": contractAddress.Hex(), + "symbol": symbol, + "name": tokenName, + "balance": value, + }, + }, nil + +} + +func (b *PluginBackend) pathERC20Transfer(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + var tokens *big.Int + config, err := b.configured(ctx, req) + if err != nil { + return nil, err + } + address := data.Get("address").(string) + name := data.Get("name").(string) + tokenAddress := common.HexToAddress(data.Get("contract").(string)) + accountJSON, err := readAccount(ctx, req, name, address) + if err != nil || accountJSON == nil { + return nil, fmt.Errorf("error reading address") + } + + chainID := util.ValidNumber(config.ChainID) + if chainID == nil { + return nil, fmt.Errorf("invalid chain ID") + } + + client, err := ethclient.Dial(config.getRPCURL()) + if err != nil { + return nil, err + } + + walletJSON, err := readWallet(ctx, req, name) + if err != nil { + return nil, err + } + + wallet, account, err := getWalletAndAccount(*walletJSON, accountJSON.Index) + if err != nil { + return nil, err + } + + instance, err := erc20.NewErc20(tokenAddress, client) + if err != nil { + return nil, err + } + callOpts := &bind.CallOpts{} + + erc20CallerSession := &erc20.Erc20CallerSession{ + Contract: &instance.Erc20Caller, // Generic contract caller binding to set the session for + CallOpts: *callOpts, // Call options to use throughout this session + } + + tokenName, err := erc20CallerSession.Name() + if err != nil { + return nil, err + } + + symbol, err := erc20CallerSession.Symbol() + if err != nil { + return nil, err + } + + decimals, err := erc20CallerSession.Decimals() + if err != nil { + return nil, err + } + + transactionParams, err := b.getBaseData(client, account.Address, data, "to") + if err != nil { + return nil, err + } + _, ok := data.GetOk("tokens") + if ok { + tokens = util.ValidNumber(data.Get("tokens").(string)) + if tokens == nil { + return nil, fmt.Errorf("number of tokens are required") + } + } else { + tokens = util.ValidNumber("0") + } + + accountJSON.Whitelist = append(accountJSON.Whitelist, config.Whitelist...) + accountJSON.Whitelist = append(accountJSON.Whitelist, walletJSON.Whitelist...) + if len(accountJSON.Whitelist) > 0 && !util.Contains(accountJSON.Whitelist, transactionParams.Address.Hex()) { + return nil, fmt.Errorf("%s violates the whitelist %+v", transactionParams.Address.Hex(), accountJSON.Whitelist) + } + err = config.BlackListed(transactionParams.Address) + if err != nil { + return nil, err + } + err = walletJSON.BlackListed(transactionParams.Address) + if err != nil { + return nil, err + } + err = accountJSON.BlackListed(transactionParams.Address) + if err != nil { + return nil, err + } + + tokenAmount := util.TokenAmount(tokens.Int64(), decimals) + transactOpts, err := b.NewWalletTransactor(chainID, wallet, account) + if err != nil { + return nil, err + } + + //transactOpts needs gas etc. + tokenSession := &erc20.Erc20Session{ + Contract: instance, // Generic contract caller binding to set the session for + CallOpts: *callOpts, // Call options to use throughout this session + TransactOpts: *transactOpts, + } + + tx, err := tokenSession.Transfer(*transactionParams.Address, tokenAmount) + if err != nil { + return nil, err + } + + var signedTxBuff bytes.Buffer + tx.EncodeRLP(&signedTxBuff) + return &logical.Response{ + Data: map[string]interface{}{ + "contract": tokenAddress.Hex(), + "symbol": symbol, + "name": tokenName, + "transaction_hash": tx.Hash().Hex(), + "signed_transaction": hexutil.Encode(signedTxBuff.Bytes()), + "from": account.Address.Hex(), + "to": transactionParams.Address.String(), + "amount": tokenAmount.String(), + "nonce": tx.Nonce(), + "gas_price": tx.GasPrice(), + "gas_limit": tx.Gas(), + }, + }, nil + +} + +func (b *PluginBackend) pathERC20TotalSupply(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + config, err := b.configured(ctx, req) + if err != nil { + return nil, err + } + + client, err := ethclient.Dial(config.getRPCURL()) + if err != nil { + return nil, err + } + + contractAddress := common.HexToAddress(data.Get("contract").(string)) + instance, err := erc20.NewErc20(contractAddress, client) + if err != nil { + return nil, err + } + callOpts := &bind.CallOpts{} + erc20CallerSession := &erc20.Erc20CallerSession{ + Contract: &instance.Erc20Caller, // Generic contract caller binding to set the session for + CallOpts: *callOpts, // Call options to use throughout this session + } + + totalSupply, err := erc20CallerSession.TotalSupply() + if err != nil { + return nil, err + } + + tokenName, err := erc20CallerSession.Name() + if err != nil { + return nil, err + } + + symbol, err := erc20CallerSession.Symbol() + if err != nil { + return nil, err + } + + decimals, err := erc20CallerSession.Decimals() + if err != nil { + return nil, err + } + + fbal := new(big.Float) + fbal.SetString(totalSupply.String()) + value := new(big.Float).Quo(fbal, big.NewFloat(math.Pow10(int(decimals)))) + + return &logical.Response{ + Data: map[string]interface{}{ + "contract": contractAddress.Hex(), + "symbol": symbol, + "name": tokenName, + "total_supply": fmt.Sprintf("%.0f", value), + }, + }, nil + +} + +func (b *PluginBackend) pathERC20Approve(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + var tokens *big.Int + config, err := b.configured(ctx, req) + if err != nil { + return nil, err + } + address := data.Get("address").(string) + name := data.Get("name").(string) + tokenAddress := common.HexToAddress(data.Get("contract").(string)) + accountJSON, err := readAccount(ctx, req, name, address) + if err != nil || accountJSON == nil { + return nil, fmt.Errorf("error reading address") + } + + chainID := util.ValidNumber(config.ChainID) + if chainID == nil { + return nil, fmt.Errorf("invalid chain ID") + } + + client, err := ethclient.Dial(config.getRPCURL()) + if err != nil { + return nil, err + } + + walletJSON, err := readWallet(ctx, req, name) + if err != nil { + return nil, err + } + + wallet, account, err := getWalletAndAccount(*walletJSON, accountJSON.Index) + if err != nil { + return nil, err + } + + instance, err := erc20.NewErc20(tokenAddress, client) + if err != nil { + return nil, err + } + callOpts := &bind.CallOpts{} + + erc20CallerSession := &erc20.Erc20CallerSession{ + Contract: &instance.Erc20Caller, // Generic contract caller binding to set the session for + CallOpts: *callOpts, // Call options to use throughout this session + } + + tokenName, err := erc20CallerSession.Name() + if err != nil { + return nil, err + } + + symbol, err := erc20CallerSession.Symbol() + if err != nil { + return nil, err + } + + decimals, err := erc20CallerSession.Decimals() + if err != nil { + return nil, err + } + + transactionParams, err := b.getBaseData(client, account.Address, data, "spender") + if err != nil { + return nil, err + } + + accountJSON.Whitelist = append(accountJSON.Whitelist, config.Whitelist...) + accountJSON.Whitelist = append(accountJSON.Whitelist, walletJSON.Whitelist...) + if len(accountJSON.Whitelist) > 0 && !util.Contains(accountJSON.Whitelist, transactionParams.Address.Hex()) { + return nil, fmt.Errorf("%s violates the whitelist %+v", transactionParams.Address.Hex(), accountJSON.Whitelist) + } + err = config.BlackListed(transactionParams.Address) + if err != nil { + return nil, err + } + err = walletJSON.BlackListed(transactionParams.Address) + if err != nil { + return nil, err + } + err = accountJSON.BlackListed(transactionParams.Address) + if err != nil { + return nil, err + } + + _, ok := data.GetOk("tokens") + if ok { + tokens = util.ValidNumber(data.Get("tokens").(string)) + if tokens == nil { + return nil, fmt.Errorf("number of tokens are required") + } + } else { + tokens = util.ValidNumber("0") + } + tokenAmount := util.TokenAmount(tokens.Int64(), decimals) + transactOpts, err := b.NewWalletTransactor(chainID, wallet, account) + if err != nil { + return nil, err + } + + //transactOpts needs gas etc. + tokenSession := &erc20.Erc20Session{ + Contract: instance, // Generic contract caller binding to set the session for + CallOpts: *callOpts, // Call options to use throughout this session + TransactOpts: *transactOpts, + } + + tx, err := tokenSession.Approve(*transactionParams.Address, tokenAmount) + if err != nil { + return nil, err + } + + var signedTxBuff bytes.Buffer + tx.EncodeRLP(&signedTxBuff) + return &logical.Response{ + Data: map[string]interface{}{ + "contract": tokenAddress.Hex(), + "symbol": symbol, + "name": tokenName, + "transaction_hash": tx.Hash().Hex(), + "signed_transaction": hexutil.Encode(signedTxBuff.Bytes()), + "from": account.Address.Hex(), + "to": transactionParams.Address.String(), + "amount": tokenAmount.String(), + "nonce": tx.Nonce(), + "gas_price": tx.GasPrice(), + "gas_limit": tx.Gas(), + }, + }, nil + +} +func (b *PluginBackend) pathERC20TransferFrom(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + var tokens *big.Int + config, err := b.configured(ctx, req) + if err != nil { + return nil, err + } + address := data.Get("address").(string) + name := data.Get("name").(string) + tokenAddress := common.HexToAddress(data.Get("contract").(string)) + accountJSON, err := readAccount(ctx, req, name, address) + if err != nil || accountJSON == nil { + return nil, fmt.Errorf("error reading address") + } + + chainID := util.ValidNumber(config.ChainID) + if chainID == nil { + return nil, fmt.Errorf("invalid chain ID") + } + + client, err := ethclient.Dial(config.getRPCURL()) + if err != nil { + return nil, err + } + + walletJSON, err := readWallet(ctx, req, name) + if err != nil { + return nil, err + } + + wallet, account, err := getWalletAndAccount(*walletJSON, accountJSON.Index) + if err != nil { + return nil, err + } + + instance, err := erc20.NewErc20(tokenAddress, client) + if err != nil { + return nil, err + } + callOpts := &bind.CallOpts{} + + erc20CallerSession := &erc20.Erc20CallerSession{ + Contract: &instance.Erc20Caller, // Generic contract caller binding to set the session for + CallOpts: *callOpts, // Call options to use throughout this session + } + + tokenName, err := erc20CallerSession.Name() + if err != nil { + return nil, err + } + + symbol, err := erc20CallerSession.Symbol() + if err != nil { + return nil, err + } + + decimals, err := erc20CallerSession.Decimals() + if err != nil { + return nil, err + } + + transactionParams, err := b.getBaseData(client, account.Address, data, "from") + if err != nil { + return nil, err + } + + accountJSON.Whitelist = append(accountJSON.Whitelist, config.Whitelist...) + accountJSON.Whitelist = append(accountJSON.Whitelist, walletJSON.Whitelist...) + if len(accountJSON.Whitelist) > 0 && !util.Contains(accountJSON.Whitelist, transactionParams.Address.Hex()) { + return nil, fmt.Errorf("%s violates the whitelist %+v", transactionParams.Address.Hex(), accountJSON.Whitelist) + } + err = config.BlackListed(transactionParams.Address) + if err != nil { + return nil, err + } + err = walletJSON.BlackListed(transactionParams.Address) + if err != nil { + return nil, err + } + err = accountJSON.BlackListed(transactionParams.Address) + if err != nil { + return nil, err + } + + _, ok := data.GetOk("tokens") + if ok { + tokens = util.ValidNumber(data.Get("tokens").(string)) + if tokens == nil { + return nil, fmt.Errorf("number of tokens are required") + } + } else { + tokens = util.ValidNumber("0") + } + tokenAmount := util.TokenAmount(tokens.Int64(), decimals) + transactOpts, err := b.NewWalletTransactor(chainID, wallet, account) + if err != nil { + return nil, err + } + + //transactOpts needs gas etc. + tokenSession := &erc20.Erc20Session{ + Contract: instance, // Generic contract caller binding to set the session for + CallOpts: *callOpts, // Call options to use throughout this session + TransactOpts: *transactOpts, + } + + tx, err := tokenSession.TransferFrom(*transactionParams.Address, account.Address, tokenAmount) + if err != nil { + return nil, err + } + + var signedTxBuff bytes.Buffer + tx.EncodeRLP(&signedTxBuff) + return &logical.Response{ + Data: map[string]interface{}{ + "contract": tokenAddress.Hex(), + "symbol": symbol, + "name": tokenName, + "transaction_hash": tx.Hash().Hex(), + "signed_transaction": hexutil.Encode(signedTxBuff.Bytes()), + "from": account.Address.Hex(), + "to": transactionParams.Address.String(), + "amount": tokenAmount.String(), + "nonce": tx.Nonce(), + "gas_price": tx.GasPrice(), + "gas_limit": tx.Gas(), + }, + }, nil + +} diff --git a/ethereum/path_erc20_test.go b/ethereum/path_erc20_test.go new file mode 100644 index 0000000..1523cec --- /dev/null +++ b/ethereum/path_erc20_test.go @@ -0,0 +1,46 @@ +// Copyright (C) Immutability, LLC - All Rights Reserved +// Unauthorized copying of this file, via any medium is strictly prohibited +// Proprietary and confidential +// Written by Jeff Ploughman , August 2019 + +package ethereum + +import ( + "context" + "testing" + + "github.com/hashicorp/vault/sdk/logical" + "gitlab.com/shearline-gateway/omisego/util" +) + +func TestTokenBalance_Read(t *testing.T) { + b, storage := getBackendConfigured(t) + + // we must create a wallet + walletPath := "wallets/fixed-token-test" + + createWallet(t, walletPath, b, storage) + account := createAccount(t, walletPath+"/accounts", b, storage) + // create an address + // harvest address + address := account["address"].(string) + // request token balance for address + tokenBalancePath := walletPath + "/accounts/" + address + "/erc-20/balanceOf" + + tokenBalanceData := map[string]interface{}{ + "contract": "0xdFADF516B98B687d2F8BB02872dd34D46B722B3C", + } + + req := &logical.Request{ + Operation: logical.ReadOperation, + Path: tokenBalancePath, + Storage: storage, + Data: tokenBalanceData, + } + resp, err := b.HandleRequest(context.Background(), req) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("err:%s resp:%#v\n", err, resp) + } + t.Log(util.PrettyPrint(resp.Data)) + +} diff --git a/makefile b/makefile index 46bc720..73d1e99 100644 --- a/makefile +++ b/makefile @@ -1,7 +1,9 @@ .ONESHELL: +DATE = $(shell date +'%s') + docker-build: - docker build -t omisego/immutability-vault-ethereum:latest . + docker build --build-arg always_upgrade="$(DATE)" -t omisego/immutability-vault-ethereum:latest . run: docker-compose -f docker/docker-compose.yml up diff --git a/scripts/smoketest.sh b/scripts/smoketest.sh index cf13c39..6c9ce53 100755 --- a/scripts/smoketest.sh +++ b/scripts/smoketest.sh @@ -8,6 +8,14 @@ RPC_URL="http://ganache:$PORT" PASSPHRASE="passion bauble hypnotic hanky kiwi effective overcast roman staleness" FUNDING_AMOUNT=100000000000000000 TEST_AMOUNT=10000000000000000 + +CONTRACTS_PATH="/home/vault/contracts/erc20/build/" +CONTRACT_SAFE_MATH="SafeMath" +CONTRACT_OWNED="Owned" +CONTRACT_FIXED_SUPPLY_TOKEN="FixedSupplyToken" +BIN_FILE=".bin" +ABI_FILE=".abi" + echo "" echo "------------------------------------------------------------------" echo "CONFIGURE MOUNT" @@ -227,3 +235,49 @@ echo "ATTEMPT TO SEND TO $ACCOUNT_BIG_BAD - SHOULD SUCCEED - GLOBALLY WHITELISTE echo "vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT1/debit to=$ACCOUNT_BIG_BAD amount=$TEST_AMOUNT" vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT1/debit to=$ACCOUNT_BIG_BAD amount=$TEST_AMOUNT echo "" + +echo "------------------------------------------------------------------" +echo "DEPLOY CONTRACT $CONTRACT_FIXED_SUPPLY_TOKEN" +echo "vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/deploy abi=@$CONTRACTS_PATH$CONTRACT_FIXED_SUPPLY_TOKEN$ABI_FILE bin=@$CONTRACTS_PATH$CONTRACT_FIXED_SUPPLY_TOKEN$BIN_FILE" +CONTRACT=$(vault write -field=contract immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/deploy abi=@$CONTRACTS_PATH$CONTRACT_FIXED_SUPPLY_TOKEN$ABI_FILE bin=@$CONTRACTS_PATH$CONTRACT_FIXED_SUPPLY_TOKEN$BIN_FILE) +echo "" +echo "------------------------------------------------------------------" +echo "DEPLOY CONTRACT $CONTRACT_OWNED" +echo "vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/deploy abi=@$CONTRACTS_PATH$CONTRACT_OWNED$ABI_FILE bin=@$CONTRACTS_PATH$CONTRACT_OWNED$BIN_FILE" +vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/deploy abi=@$CONTRACTS_PATH$CONTRACT_OWNED$ABI_FILE bin=@$CONTRACTS_PATH$CONTRACT_OWNED$BIN_FILE +echo "" +echo "------------------------------------------------------------------" +echo "DEPLOY CONTRACT $CONTRACT_SAFE_MATH" +echo "vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/deploy abi=@$CONTRACTS_PATH$CONTRACT_SAFE_MATH$ABI_FILE bin=@$CONTRACTS_PATH$CONTRACT_SAFE_MATH$BIN_FILE" +vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/deploy abi=@$CONTRACTS_PATH$CONTRACT_SAFE_MATH$ABI_FILE bin=@$CONTRACTS_PATH$CONTRACT_SAFE_MATH$BIN_FILE +echo "" +echo "------------------------------------------------------------------" +echo "SIGN RAW TX FROM ACCOUNT" +echo "vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/sign-tx to='$ACCOUNT1' data='hello' amount=1000000000000000" +vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/sign-tx to="$ACCOUNT1" data="hello" amount=1000000000000000 +echo "" +echo "------------------------------------------------------------------" +echo "SIGN RAW TX FROM ACCOUNT WITH HEX ENCODING OF DATA" +echo "vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/sign-tx to='$ACCOUNT1' data='fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19' encoding='hex' amount=1000000000000000" +vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/sign-tx to="$ACCOUNT1" data="fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19" encoding="hex" amount=1000000000000000 +echo "" +echo "------------------------------------------------------------------" +echo "READ TOKEN TOTAL SUPPLY" +echo "vault read immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/erc-20/totalSupply contract='$CONTRACT'" +vault read immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/erc-20/totalSupply contract="$CONTRACT" +echo "" +echo "------------------------------------------------------------------"`` +echo "READ TOKEN BALANCE AT ACCOUNT" +echo "vault read immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/erc-20/balanceOf contract='$CONTRACT'" +vault read immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/erc-20/balanceOf contract="$CONTRACT" +echo "" +echo "------------------------------------------------------------------" +echo "TRANSFER TOKEN FROM ACCOUNT" +echo "vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/erc-20/transfer contract='$CONTRACT' to='$ACCOUNT2' tokens=23" +vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/erc-20/transfer contract="$CONTRACT" to="$ACCOUNT2" tokens=23 +echo "" +echo "------------------------------------------------------------------" +echo "APPROVE TOKEN TRANSFER FROM ACCOUNT TO ANOTHER" +echo "vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/erc-20/approve contract='$CONTRACT' spender='$ACCOUNT2' tokens=230" +vault write immutability-eth-plugin/wallets/test-wallet-2/accounts/$ACCOUNT0/erc-20/approve contract="$CONTRACT" spender="$ACCOUNT2" tokens=230 +echo "" diff --git a/util/helper.go b/util/helper.go index 2372969..774a8af 100644 --- a/util/helper.go +++ b/util/helper.go @@ -318,3 +318,16 @@ func WriteKeyFile(file string, content []byte) error { f.Close() return os.Rename(f.Name(), file) } + +// TokenAmount does the requisite math on tokens +func TokenAmount(amount int64, decimals uint8) *big.Int { + var bigDecimal big.Int + bigDecimal.SetString(fmt.Sprintf("%d", decimals), 10) + power := Pow(10, bigDecimal.Int64()) + + var bigPower, _ = new(big.Int).SetString(fmt.Sprintf("%d", power), 10) + var bigAmount, _ = new(big.Int).SetString(fmt.Sprintf("%d", amount), 10) + + var bigProduct = new(big.Int) + return bigProduct.Mul(bigPower, bigAmount) +}