diff --git a/src/strategies/index.ts b/src/strategies/index.ts index 8e2cbb25a..87640ac90 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -334,6 +334,7 @@ import * as solvVoucherClaimable from './solv-voucher-claimable'; import * as h2o from './h2o'; import * as dopamine from './dopamine'; import * as lrcL2SubgraphBalanceOf from './lrc-l2-subgraph-balance-of'; +import * as rariFuse from './rari-fuse'; const strategies = { 'forta-shares': fortaShares, @@ -671,7 +672,8 @@ const strategies = { apeswap, h2o, dopamine, - 'lrc-l2-subgraph-balance-of': lrcL2SubgraphBalanceOf + 'lrc-l2-subgraph-balance-of': lrcL2SubgraphBalanceOf, + 'rari-fuse': rariFuse }; Object.keys(strategies).forEach(function (strategyName) { diff --git a/src/strategies/rari-fuse/README.md b/src/strategies/rari-fuse/README.md new file mode 100644 index 000000000..5e5a70b1c --- /dev/null +++ b/src/strategies/rari-fuse/README.md @@ -0,0 +1,22 @@ +# rari-fuse + +Returns the voter's underlying collateral balances in a given Rari Fuse market (fToken). + +Here is an example of parameters: + +- `symbol` - (**Optional**, `string`) Symbol of the underlying ERC20 token +- `token` - (**Required**, `string`) Address of the underlying token. +- `fToken` - (**Required**, `string`) Address of the fToken (Rari Fuse market) + +```json +{ + "symbol": "BANK", + "token": "0x2d94AA3e47d9D5024503Ca8491fcE9A2fB4DA198", + "fToken": "0x250316B3E46600417654b13bEa68b5f64D61E609" +} +``` + +## Reference + +For details about exchange rate between fTokens and underlying tokens, see +https://docs.rari.capital/fuse/#interpreting-exchange-rates diff --git a/src/strategies/rari-fuse/examples.json b/src/strategies/rari-fuse/examples.json new file mode 100644 index 000000000..a733c48b6 --- /dev/null +++ b/src/strategies/rari-fuse/examples.json @@ -0,0 +1,22 @@ +[ + { + "name": "Example query", + "strategy": { + "name": "rari-fuse", + "params": { + "symbol": "BANK", + "token": "0x2d94AA3e47d9D5024503Ca8491fcE9A2fB4DA198", + "fToken": "0x250316B3E46600417654b13bEa68b5f64D61E609" + } + }, + "network": "1", + "addresses": [ + "0x3839AcF1ee7699D1F46b1BE840D8aD8317FDf757", + "0x0cf861f96378dbd5194d74cbe6b0424fafaed940", + "0x0f1d41cc51e97dc9d0cad80dc681777eed3675e3", + "0xb6ac0341fcf3fb507a8208d34a97f13779e1393d", + "0x173ff4db38c3fcde0584f8ea7930c44969a29ba4" + ], + "snapshot": 14482171 + } +] diff --git a/src/strategies/rari-fuse/index.ts b/src/strategies/rari-fuse/index.ts new file mode 100644 index 000000000..8979152e9 --- /dev/null +++ b/src/strategies/rari-fuse/index.ts @@ -0,0 +1,62 @@ +import { BigNumber } from '@ethersproject/bignumber'; +import { formatUnits } from '@ethersproject/units'; +import { Multicaller } from '../../utils'; + +export const author = 'MantisClone'; +export const version = '0.1.0'; + +const abi = [ + 'function underlying() public view returns (address)', + 'function decimals() public view returns (uint8)', + 'function exchangeRateStored() public view returns (uint256)', + 'function balanceOf(address owner) external returns (uint256)' +]; + +export async function strategy( + space, + network, + provider, + addresses, + options, + snapshot +): Promise> { + const blockTag = typeof snapshot === 'number' ? snapshot : 'latest'; + + const multi = new Multicaller(network, provider, abi, { blockTag }); + multi.call('underlying', options.fToken, 'underlying', []); + multi.call('tokenDecimals', options.token, 'decimals', []); + multi.call('fTokenDecimals', options.fToken, 'decimals', []); + multi.call('exchangeRate', options.fToken, 'exchangeRateStored', []); + addresses.forEach((address) => + multi.call(`fTokenBalances.${address}`, options.fToken, 'balanceOf', [ + address + ]) + ); + const result = await multi.execute(); + + const underlying: string = result.underlying; + const tokenDecimals: BigNumber = result.tokenDecimals; + const fTokenDecimals: BigNumber = result.fTokenDecimals; + const exchangeRate: BigNumber = result.exchangeRate; + const fTokenBalances: Record = result.fTokenBalances; + + if (options.token !== underlying) { + throw new Error( + `token parameter doesn't match fToken.underlying(). token=${options.token}, underlying=${underlying}` + ); + } + + const mantissa: BigNumber = BigNumber.from(18) + .add(tokenDecimals) + .sub(fTokenDecimals); + const divisor: BigNumber = BigNumber.from(10).pow(mantissa); + + return Object.fromEntries( + Object.entries(fTokenBalances).map(([address, balance]) => [ + address, + parseFloat( + formatUnits(balance.mul(exchangeRate).div(divisor), fTokenDecimals) + ) + ]) + ); +} diff --git a/src/strategies/rari-fuse/schema.json b/src/strategies/rari-fuse/schema.json new file mode 100644 index 000000000..71e3fecc6 --- /dev/null +++ b/src/strategies/rari-fuse/schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/Strategy", + "definitions": { + "Strategy": { + "title": "Strategy", + "type": "object", + "properties": { + "symbol": { + "type": "string", + "title": "symbol", + "examples": ["e.g. BANK"], + "maxLength": 16 + }, + "token": { + "type": "string", + "title": "token", + "examples": ["e.g. 0x2d94AA3e47d9D5024503Ca8491fcE9A2fB4DA198"], + "pattern": "^0x[a-fA-F0-9]{40}$", + "minLength": 42, + "maxLength": 42 + }, + "fToken": { + "type": "string", + "title": "fToken", + "examples": ["e.g. 0x250316B3E46600417654b13bEa68b5f64D61E609"], + "pattern": "^0x[a-fA-F0-9]{40}$", + "minLength": 42, + "maxLength": 42 + } + }, + "required": ["token", "fToken"], + "additionalProperties": false + } + } +}