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 }