github.com/Blockdaemon/celo-blockchain@v0.0.0-20200129231733-e667f6b08419/contract_comm/currency/currency.go (about)

     1  // Copyright 2017 The Celo Authors
     2  // This file is part of the celo library.
     3  //
     4  // The celo library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The celo library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the celo library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package currency
    18  
    19  import (
    20  	"math/big"
    21  	"strings"
    22  
    23  	"github.com/ethereum/go-ethereum/accounts/abi"
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/contract_comm"
    26  	"github.com/ethereum/go-ethereum/contract_comm/errors"
    27  	"github.com/ethereum/go-ethereum/core/types"
    28  	"github.com/ethereum/go-ethereum/core/vm"
    29  	"github.com/ethereum/go-ethereum/log"
    30  	"github.com/ethereum/go-ethereum/params"
    31  )
    32  
    33  const (
    34  	// This is taken from celo-monorepo/packages/protocol/build/<env>/contracts/SortedOracles.json
    35  	medianRateABI = `[
    36      {
    37        "constant": true,
    38        "inputs": [
    39          {
    40            "name": "token",
    41            "type": "address"
    42          }
    43        ],
    44        "name": "medianRate",
    45        "outputs": [
    46          {
    47            "name": "",
    48            "type": "uint128"
    49          },
    50          {
    51            "name": "",
    52            "type": "uint128"
    53          }
    54        ],
    55        "payable": false,
    56        "stateMutability": "view",
    57        "type": "function"
    58      }]`
    59  
    60  	// This is taken from celo-monorepo/packages/protocol/build/<env>/contracts/ERC20.json
    61  	balanceOfABI = `[{"constant": true,
    62                            "inputs": [
    63                                 {
    64                                     "name": "who",
    65                                     "type": "address"
    66                                 }
    67                            ],
    68                            "name": "balanceOf",
    69                            "outputs": [
    70                                 {
    71                                     "name": "",
    72                                     "type": "uint256"
    73                                 }
    74                            ],
    75                            "payable": false,
    76                            "stateMutability": "view",
    77                            "type": "function"
    78                           }]`
    79  
    80  	// This is taken from celo-monorepo/packages/protocol/build/<env>/contracts/FeeCurrency.json
    81  	getWhitelistABI = `[{"constant": true,
    82  	                     "inputs": [],
    83  	                     "name": "getWhitelist",
    84  	                     "outputs": [
    85  			          {
    86  			              "name": "",
    87  				      "type": "address[]"
    88  				  }
    89  			     ],
    90  			     "payable": false,
    91  			     "stateMutability": "view",
    92  			     "type": "function"
    93  			    }]`
    94  )
    95  
    96  var (
    97  	cgExchangeRateNum = big.NewInt(1)
    98  	cgExchangeRateDen = big.NewInt(1)
    99  
   100  	medianRateFuncABI, _   = abi.JSON(strings.NewReader(medianRateABI))
   101  	balanceOfFuncABI, _    = abi.JSON(strings.NewReader(balanceOfABI))
   102  	getWhitelistFuncABI, _ = abi.JSON(strings.NewReader(getWhitelistABI))
   103  )
   104  
   105  type exchangeRate struct {
   106  	Numerator   *big.Int
   107  	Denominator *big.Int
   108  }
   109  
   110  func ConvertToGold(val *big.Int, currencyFrom *common.Address) (*big.Int, error) {
   111  	celoGoldAddress, err := contract_comm.GetRegisteredAddress(params.GoldTokenRegistryId, nil, nil)
   112  	if err == errors.ErrSmartContractNotDeployed || err == errors.ErrRegistryContractNotDeployed {
   113  		log.Warn("Registry address lookup failed", "err", err)
   114  		return val, err
   115  	}
   116  
   117  	if currencyFrom == celoGoldAddress {
   118  		return val, err
   119  	} else if err != nil {
   120  		log.Error(err.Error())
   121  		return val, err
   122  	}
   123  	return Convert(val, currencyFrom, celoGoldAddress)
   124  }
   125  
   126  // NOTE (jarmg 4/24/19): values are rounded down which can cause
   127  // an estimate to be off by 1 (at most)
   128  func Convert(val *big.Int, currencyFrom *common.Address, currencyTo *common.Address) (*big.Int, error) {
   129  	exchangeRateFrom, err1 := getExchangeRate(currencyFrom)
   130  	exchangeRateTo, err2 := getExchangeRate(currencyTo)
   131  
   132  	if err1 != nil || err2 != nil {
   133  		log.Error("Convert - Error in retreiving currency exchange rates")
   134  		if err1 != nil {
   135  			return nil, err1
   136  		}
   137  		if err2 != nil {
   138  			return nil, err2
   139  		}
   140  	}
   141  
   142  	// Given value of val and rates n1/d1 and n2/d2 the function below does
   143  	// (val * d1 * n2) / (n1 * d2)
   144  	numerator := new(big.Int).Mul(val, new(big.Int).Mul(exchangeRateFrom.Denominator, exchangeRateTo.Numerator))
   145  	denominator := new(big.Int).Mul(exchangeRateFrom.Numerator, exchangeRateTo.Denominator)
   146  	return new(big.Int).Div(numerator, denominator), nil
   147  }
   148  
   149  func Cmp(val1 *big.Int, currency1 *common.Address, val2 *big.Int, currency2 *common.Address) int {
   150  	if currency1 == currency2 {
   151  		return val1.Cmp(val2)
   152  	}
   153  
   154  	exchangeRate1, err1 := getExchangeRate(currency1)
   155  	exchangeRate2, err2 := getExchangeRate(currency2)
   156  
   157  	if err1 != nil || err2 != nil {
   158  		currency1Output := "nil"
   159  		if currency1 != nil {
   160  			currency1Output = currency1.Hex()
   161  		}
   162  		currency2Output := "nil"
   163  		if currency2 != nil {
   164  			currency2Output = currency2.Hex()
   165  		}
   166  		log.Warn("Error in retrieving exchange rate.  Will do comparison of two values without exchange rate conversion.", "currency1", currency1Output, "err1", err1, "currency2", currency2Output, "err2", err2)
   167  		return val1.Cmp(val2)
   168  	}
   169  
   170  	// Below code block is basically evaluating this comparison:
   171  	// val1 * exchangeRate1.Denominator/exchangeRate1.Numerator < val2 * exchangeRate2.Denominator/exchangeRate2.Numerator
   172  	// It will transform that comparison to this, to remove having to deal with fractional values.
   173  	// val1 * exchangeRate1.Denominator * exchangeRate2.Numerator < val2 * exchangeRate2.Denominator * exchangeRate1.Numerator
   174  	leftSide := new(big.Int).Mul(val1, new(big.Int).Mul(exchangeRate1.Denominator, exchangeRate2.Numerator))
   175  	rightSide := new(big.Int).Mul(val2, new(big.Int).Mul(exchangeRate2.Denominator, exchangeRate1.Numerator))
   176  	return leftSide.Cmp(rightSide)
   177  }
   178  
   179  func getExchangeRate(currencyAddress *common.Address) (*exchangeRate, error) {
   180  	var (
   181  		returnArray [2]*big.Int
   182  		leftoverGas uint64
   183  	)
   184  
   185  	if currencyAddress == nil {
   186  		return &exchangeRate{cgExchangeRateNum, cgExchangeRateDen}, nil
   187  	} else {
   188  		if leftoverGas, err := contract_comm.MakeStaticCall(params.SortedOraclesRegistryId, medianRateFuncABI, "medianRate", []interface{}{currencyAddress}, &returnArray, params.MaxGasForMedianRate, nil, nil); err != nil {
   189  			if err == errors.ErrSmartContractNotDeployed {
   190  				log.Warn("Registry address lookup failed", "err", err)
   191  				return &exchangeRate{big.NewInt(1), big.NewInt(1)}, err
   192  			} else {
   193  				log.Error("medianRate invocation error", "feeCurrencyAddress", currencyAddress.Hex(), "leftoverGas", leftoverGas, "err", err)
   194  				return &exchangeRate{big.NewInt(1), big.NewInt(1)}, err
   195  			}
   196  		}
   197  	}
   198  	log.Trace("medianRate invocation success", "feeCurrencyAddress", currencyAddress, "returnArray", returnArray, "leftoverGas", leftoverGas)
   199  	return &exchangeRate{returnArray[0], returnArray[1]}, nil
   200  }
   201  
   202  // This function will retrieve the balance of an ERC20 token.
   203  func GetBalanceOf(accountOwner common.Address, contractAddress common.Address, gas uint64, header *types.Header, state vm.StateDB) (result *big.Int, gasUsed uint64, err error) {
   204  	log.Trace("GetBalanceOf() Called", "accountOwner", accountOwner.Hex(), "contractAddress", contractAddress, "gas", gas)
   205  
   206  	leftoverGas, err := contract_comm.MakeStaticCallWithAddress(contractAddress, balanceOfFuncABI, "balanceOf", []interface{}{accountOwner}, &result, gas, header, state)
   207  
   208  	if err != nil {
   209  		log.Error("GetBalanceOf evm invocation error", "leftoverGas", leftoverGas, "err", err)
   210  		gasUsed = gas - leftoverGas
   211  		return
   212  	} else {
   213  		gasUsed = gas - leftoverGas
   214  		log.Trace("GetBalanceOf evm invocation success", "accountOwner", accountOwner.Hex(), "Balance", result.String(), "gas used", gasUsed)
   215  		return
   216  	}
   217  }
   218  
   219  // ------------------------------
   220  // FeeCurrencyWhiteList Functions
   221  //-------------------------------
   222  func retrieveWhitelist(header *types.Header, state vm.StateDB) ([]common.Address, error) {
   223  	returnList := []common.Address{}
   224  
   225  	_, err := contract_comm.MakeStaticCall(params.FeeCurrencyWhitelistRegistryId, getWhitelistFuncABI, "getWhitelist", []interface{}{}, &returnList, params.MaxGasForGetWhiteList, header, state)
   226  	if err != nil {
   227  		if err == errors.ErrSmartContractNotDeployed {
   228  			log.Warn("Registry address lookup failed", "err", err)
   229  		} else {
   230  			log.Error("getWhitelist invocation failed", "err", err)
   231  		}
   232  		return returnList, err
   233  	}
   234  
   235  	log.Trace("getWhitelist invocation success")
   236  	return returnList, err
   237  }
   238  
   239  func IsWhitelisted(currencyAddress common.Address, header *types.Header, state vm.StateDB) bool {
   240  	whitelist, err := retrieveWhitelist(header, state)
   241  	if err != nil {
   242  		log.Warn("Failed to get fee currency whitelist", "err", err)
   243  		return true
   244  	}
   245  	return containsCurrency(currencyAddress, whitelist)
   246  }
   247  
   248  func containsCurrency(currencyAddr common.Address, currencyList []common.Address) bool {
   249  	for _, addr := range currencyList {
   250  		if addr == currencyAddr {
   251  			return true
   252  		}
   253  	}
   254  	return false
   255  }
   256  
   257  func CurrencyWhitelist(header *types.Header, state vm.StateDB) ([]common.Address, error) {
   258  	whitelist, err := retrieveWhitelist(header, state)
   259  	if err != nil {
   260  		log.Warn("Failed to get fee currency whitelist", "err", err)
   261  	}
   262  	return whitelist, err
   263  }