Skip to content

Commit 1565716

Browse files
committed
Add SortedOracles SC wrapper
1 parent c273072 commit 1565716

File tree

1 file changed

+297
-0
lines changed

1 file changed

+297
-0
lines changed
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
import sys
2+
from typing import List
3+
4+
from sdk.contracts.base_wrapper import BaseWrapper
5+
from sdk.registry import Registry
6+
7+
from web3 import Web3
8+
9+
10+
# TODO: test when other called SC wrappers will be written and callable
11+
class SortedOracles(BaseWrapper):
12+
"""
13+
Currency price oracle contract
14+
15+
Attributes:
16+
web3: Web3
17+
Web3 object
18+
registry: Registry
19+
Registry object
20+
address: str
21+
Contract's address
22+
abi: list
23+
Contract's ABI
24+
wallet: Wallet
25+
Wallet object to sign transactions
26+
"""
27+
28+
def __init__(self, web3: Web3, registry: Registry, address: str, abi: list, wallet: 'Wallet' = None):
29+
super().__init__(web3, registry, wallet=wallet)
30+
self.web3 = web3
31+
self.address = address
32+
self._contract = self.web3.eth.contract(self.address, abi=abi)
33+
self.__wallet = wallet
34+
35+
def num_rates(self, token: str) -> int:
36+
"""
37+
Gets the number of rates that have been reported for the given token
38+
39+
Parameters:
40+
token: str
41+
The CeloToken token for which the CELO exchange rate is being reported
42+
"GoldToken" or "StableToken"
43+
Returns:
44+
int
45+
The number of reported oracle rates for `token`
46+
"""
47+
token_address = self.registry.registry.functions.getAddressForString(token).call()
48+
49+
return self._contract.functions.numRates(token_address).call()
50+
51+
def median_rate(self, token: str) -> dict:
52+
"""
53+
Returns the median rate for the given token
54+
55+
Parameters:
56+
token: str
57+
The CeloToken token for which the CELO exchange rate is being reported
58+
"GoldToken" or "StableToken"
59+
Returns:
60+
dict
61+
The median exchange rate for `token`, expressed as:
62+
amount of that token / equivalent amount in CELO
63+
"""
64+
token_address = self.registry.registry.functions.getAddressForString(token).call()
65+
func_call = self._contract.functions.medianRate(token_address).call()
66+
67+
return {'rate': func_call[0] / func_call[1]}
68+
69+
def is_oracle(self, token: str, oracle: str) -> bool:
70+
"""
71+
Checks if the given address is whitelisted as an oracle for the token
72+
73+
Parameters:
74+
token: str
75+
The CeloToken token
76+
"GoldToken" or "StableToken"
77+
oracle: str
78+
The address that we're checking the oracle status of
79+
Returns:
80+
bool
81+
boolean describing whether this account is an oracle
82+
"""
83+
token_address = self.registry.registry.functions.getAddressForString(token).call()
84+
85+
return self._contract.functions.isOracle(token_address, oracle).call()
86+
87+
def get_oracles(self, token: str) -> List[str]:
88+
"""
89+
Returns the list of whitelisted oracles for a given token
90+
91+
Parameters:
92+
token: str
93+
The CeloToken token
94+
"GoldToken" or "StableToken"
95+
Returns:
96+
List[str]
97+
The list of whitelisted oracles for a given token
98+
"""
99+
token_address = self.registry.registry.functions.getAddressForString(token).call()
100+
101+
return self._contract.functions.getOracles(token_address).call()
102+
103+
def report_expiry_seconds(self) -> int:
104+
"""
105+
Returns the report expiry parameter
106+
107+
Returns:
108+
int
109+
Current report expiry
110+
"""
111+
return self._contract.functions.reportExpirySeconds().call()
112+
113+
def is_oldest_report_expired(self, token: str) -> List[bool, str]:
114+
"""
115+
Checks if the oldest report for a given token is expired
116+
117+
Parameters:
118+
token: str
119+
The token for which to check reports
120+
"GoldToken" or "StableToken"
121+
Returns:
122+
List[bool, str]
123+
"""
124+
token_address = self.registry.registry.functions.getAddressForString(token).call()
125+
126+
return self._contract.functions.isOldestReportExpired(token_address).call()
127+
128+
def remove_expired_reports(self, token: str, num_reports: int = None) -> str:
129+
"""
130+
Removes expired reports, if any exist
131+
132+
Parameters:
133+
token: str
134+
The token for which to check reports
135+
"GoldToken" or "StableToken"
136+
num_reports: int
137+
The upper-limit of reports to remove. For example, if there
138+
are 2 expired reports, and this param is 5, it will only remove the 2 that
139+
are expired
140+
Returns:
141+
str
142+
Transaction hash
143+
"""
144+
token_address = self.registry.registry.functions.getAddressForString(token).call()
145+
146+
if num_reports == None:
147+
num_reports = len(self.get_reports(token)) - 1
148+
149+
func_call = self._contract.functions.removeExpiredReports(token_address, num_reports)
150+
151+
return self.__wallet.send_transaction(func_call)
152+
153+
def report(self, token: str, value: int, oracle_address: str) -> str:
154+
"""
155+
Updates an oracle value and the median
156+
157+
Parameters:
158+
token: str
159+
The token for which the CELO exchange rate is being reported
160+
"GoldToken" or "StableToken"
161+
value: int
162+
The amount of `token` equal to one CELO
163+
oracle_address: str
164+
Returns:
165+
str
166+
Transaction hash
167+
"""
168+
token_address = self.registry.registry.functions.getAddressForString(token).call()
169+
lesser_greater = self.find_lesser_and_greater_keys(token, value, oracle_address)
170+
171+
func_call = self._contract.functions.report(token_address, value, lesser_greater['lesser_key'], lesser_greater['greater_key'])
172+
173+
return self.__wallet.send_transaction(func_call, parameters={'from': oracle_address})
174+
175+
def report_stable_token(self, value: int, oracle_address: str) -> str:
176+
"""
177+
Updates an oracle value and the median
178+
179+
Parameters:
180+
value: int
181+
The amount of US Dollars equal to one CELO
182+
oracle_address: str
183+
Returns:
184+
str
185+
Transaction hash
186+
"""
187+
return self.report('StableToken', value, oracle_address)
188+
189+
def get_config(self) -> dict:
190+
"""
191+
Returns current configuration parameters
192+
193+
Returns:
194+
dict
195+
{'report_expiry_seconds': int}
196+
"""
197+
return {'report_expiry_seconds': self.report_expiry_seconds()}
198+
199+
def get_stable_token_rates(self) -> List[dict]:
200+
"""
201+
Helper function to get the rates for StableToken, by passing the address
202+
of StableToken to `getRates`
203+
204+
Returns:
205+
List[dict]
206+
"""
207+
return self.get_rates('StableToken')
208+
209+
def get_rates(self, token: str) -> List[dict]:
210+
"""
211+
Gets all elements from the doubly linked list
212+
213+
Parameters:
214+
token: str
215+
The CeloToken representing the token for which the Celo
216+
Gold exchange rate is being reported. Example: CeloContract.StableToken
217+
Returns:
218+
List[dict]
219+
An unpacked list of elements from largest to smallest
220+
"""
221+
token_address = self.registry.registry.functions.getAddressForString(token).call()
222+
response = self._contract.functions.getRates(token_address).call()
223+
224+
rates = []
225+
for ind, addr in enumerate(response[0]):
226+
med_rel_index = response[2][ind]
227+
rates.append({'address': addr, 'rate': response[1][ind], 'median_relation': med_rel_index})
228+
229+
return rates
230+
231+
def get_timestamps(self, token: str) -> List[dict]:
232+
"""
233+
Gets all elements from the doubly linked list
234+
235+
Parameters:
236+
token: str
237+
The CeloToken representing the token for which the Celo
238+
Gold exchange rate is being reported. Example: CeloContract.StableToken
239+
Returns:
240+
List[dict]
241+
An unpacked list of elements from largest to smallest
242+
"""
243+
token_address = self.registry.registry.functions.getAddressForString(token).call()
244+
response = self._contract.functions.getTimestamps(token_address).call()
245+
246+
timestamps = []
247+
for ind, addr in enumerate(response[0]):
248+
med_rel_index = response[2][ind]
249+
timestamps.append({'address': addr, 'timestamp': response[1][ind], 'median_relation': med_rel_index})
250+
251+
return timestamps
252+
253+
def get_reports(self, token: str) -> List[dict]:
254+
"""
255+
Parameters:
256+
token: str
257+
Returns:
258+
List[dict]
259+
"""
260+
rates = self.get_rates(token)
261+
timestamp = self.get_timestamps(token)
262+
263+
reports = []
264+
for rate in rates:
265+
match = 0
266+
for t in timestamp:
267+
if t['address'] == rate['address']:
268+
match = t['timestamp']
269+
break
270+
reports.append({'address': rate['address'], 'rate': rate['rate'], 'timestamp': match})
271+
272+
return reports
273+
274+
def find_lesser_and_greater_keys(self, token: str, value: int, oracle_address: str) -> dict:
275+
"""
276+
Parameters:
277+
token: str
278+
value: int
279+
oracle_address: str
280+
Returns:
281+
dict
282+
"""
283+
current_rates = self.get_rates(token)
284+
285+
greater_key = self.null_address
286+
lesser_key = self.null_address
287+
288+
# This leverages the fact that the currentRates are already sorted from
289+
# greatest to lowest value
290+
for rate in current_rates:
291+
if rate['address'] != oracle_address:
292+
if rate['rate'] <= value:
293+
lesser_key = rate['address']
294+
break
295+
greater_key = rate['address']
296+
297+
return {'lesser_key': lesser_key, 'greater_key': greater_key}

0 commit comments

Comments
 (0)