diff --git a/src/store/account/effects.ts b/src/store/account/effects.ts index 31f2ee5d7..cf0886116 100644 --- a/src/store/account/effects.ts +++ b/src/store/account/effects.ts @@ -65,6 +65,13 @@ export const getStakingProviderOperatorInfo = async ( }) ) + listenerApi.dispatch( + setMappedOperator({ + appName: "pre", + operator: mappedOperators.pre, + }) + ) + listenerApi.dispatch(operatorMappingInitialFetchDone()) } catch (error) { listenerApi.dispatch( diff --git a/src/store/account/slice.ts b/src/store/account/slice.ts index e091c09cd..3dd867b75 100644 --- a/src/store/account/slice.ts +++ b/src/store/account/slice.ts @@ -19,6 +19,7 @@ interface AccountState { mappedOperators: { tbtc: string randomBeacon: string + pre: string } }> } @@ -33,6 +34,7 @@ export const accountSlice = createSlice({ mappedOperators: { tbtc: AddressZero, randomBeacon: AddressZero, + pre: AddressZero, }, }, isFetching: false, @@ -52,7 +54,7 @@ export const accountSlice = createSlice({ setMappedOperator: ( state: AccountState, action: PayloadAction<{ - appName: StakingAppName + appName: StakingAppName | "pre" operator: string }> ) => { diff --git a/src/threshold-ts/applications/index.ts b/src/threshold-ts/applications/index.ts index aaf53b10f..5eac26e4a 100644 --- a/src/threshold-ts/applications/index.ts +++ b/src/threshold-ts/applications/index.ts @@ -389,3 +389,5 @@ export class Application implements IApplication { return await this._application.operatorToStakingProvider(operator) } } + +export * from "./preApplication" diff --git a/src/threshold-ts/applications/preApplication.ts b/src/threshold-ts/applications/preApplication.ts new file mode 100644 index 000000000..db93435c1 --- /dev/null +++ b/src/threshold-ts/applications/preApplication.ts @@ -0,0 +1,118 @@ +import { + Contract, + BigNumber, + BigNumberish, + ContractTransaction, + ContractInterface, +} from "ethers" +import { AuthorizationParameters, StakingProviderAppInfo } from "." +import { IMulticall } from "../multicall" +import { IStaking } from "../staking" +import { EthereumConfig } from "../types" +import { getContract, isAddressZero } from "../utils" + +export interface PREStakingProviderInfo { + /** + * Operator address mapped to a given staking provider + */ + operator: string + /** + * Info if operator is confirmed + */ + operatorConfirmed: boolean + /** + * Timestamp where operator were bonded + */ + operatorStartTimestamp: Number +} + +export interface IPREApplication { + /** + * Application address. + */ + address: string + + /** + * Application contract. + */ + contract: Contract + + /** + * Checks if the operator for the given staking provider is mapped + * @param stakingProvider Staking provider address + * @returns boolean value which informs if operator is mapped for the given + * staking provider + */ + isOperatorMapped(stakingProvider: string): Promise + + // /** + // * Copied from: + // * https://github.com/nucypher/nucypher/blob/development/nucypher/blockchain/eth/sol/source/contracts/SimplePREApplication.sol#L172-L176 + // * + // * Bond operator + // * @param stakingProvider Staking provider address + // * @param operator Operator address. Must be a real address, not a contract + // */ + // bondOperator( + // stakingProvider: string, + // operator: string + // ): Promise + + // /** + // * Copied from: + // * https://github.com/nucypher/nucypher/blob/development/nucypher/blockchain/eth/sol/source/contracts/SimplePREApplication.sol#L213-L215 + // * + // * Make a confirmation by operator + // */ + // confirmOperatorAddress(): Promise + + /** + * Used to get a registered operator mapped to the given staking provider + * @param stakingProvider Staking provider address + */ + stakingProviderToOperator(stakingProvider: string): Promise + + /** + * Used to get staking provider address mapped to the given registered + * operator address + * @param operator Operator address + */ + operatorToStakingProvider(operator: string): Promise +} + +export class PREApplication implements IPREApplication { + private _application: Contract + // private _staking: IStaking + private _multicall: IMulticall + + constructor( + multicall: IMulticall, + config: EthereumConfig & { address: string; abi: ContractInterface } + ) { + const { address, abi, providerOrSigner, account } = config + this._application = getContract(address, abi, providerOrSigner, account) + this._multicall = multicall + } + + get address() { + return this._application.address + } + get contract() { + return this._application + } + + isOperatorMapped = async (stakingProvider: string): Promise => { + const { operator, operatorConfirmed } = + await this._application.stakingProviderInfo(stakingProvider) + + return !isAddressZero(operator) && operatorConfirmed + } + + stakingProviderToOperator(stakingProvider: string): Promise { + return this._application.getOperatorFromStakingProvider(stakingProvider) + } + + operatorToStakingProvider(operator: string): Promise { + return this._application.stakingProviderFromOperator(operator) + } +} diff --git a/src/threshold-ts/mas/__test__/mas.test.ts b/src/threshold-ts/mas/__test__/mas.test.ts index 88760890e..3781e57b7 100644 --- a/src/threshold-ts/mas/__test__/mas.test.ts +++ b/src/threshold-ts/mas/__test__/mas.test.ts @@ -105,10 +105,12 @@ describe("Multi app staking test", () => { const mockOperator = "0x4" const mappedOperatorTbtc = mockOperator const mappedOperatorRandomBeacon = mockOperator + const mappedOperatorPre = mockOperator const mulitcallMockResult = [ [mappedOperatorTbtc], [mappedOperatorRandomBeacon], + [mappedOperatorPre], ] const spyOnMulticall = jest .spyOn(multicall, "aggregate") @@ -131,10 +133,17 @@ describe("Multi app staking test", () => { method: "stakingProviderToOperator", args: [mockStakingProvider], }, + { + interface: mas.pre.contract.interface, + address: mas.pre.address, + method: "getOperatorFromStakingProvider", + args: [mockStakingProvider], + }, ]) expect(result).toEqual({ tbtc: mappedOperatorTbtc, randomBeacon: mappedOperatorRandomBeacon, + pre: mappedOperatorPre, }) }) }) diff --git a/src/threshold-ts/mas/index.ts b/src/threshold-ts/mas/index.ts index f63a84e9c..790fb12d0 100644 --- a/src/threshold-ts/mas/index.ts +++ b/src/threshold-ts/mas/index.ts @@ -1,13 +1,18 @@ import RandomBeacon from "@keep-network/random-beacon/artifacts/RandomBeacon.json" import WalletRegistry from "@keep-network/ecdsa/artifacts/WalletRegistry.json" +import SimplePREApplicationABI from "../utils/abi/SimplePreApplication.json" + import { Application, AuthorizationParameters, IApplication, + IPREApplication, + PREApplication, } from "../applications" import { IMulticall, ContractCall } from "../multicall" import { IStaking } from "../staking" import { EthereumConfig } from "../types" +import { AddressZero } from "../utils" export interface SupportedAppAuthorizationParameters { tbtc: AuthorizationParameters @@ -17,19 +22,32 @@ export interface SupportedAppAuthorizationParameters { export interface MappedOperatorsForStakingProvider { tbtc: string randomBeacon: string + pre: string } +export const PRE_ADDRESSESS = { + 1: "0x7E01c9c03FD3737294dbD7630a34845B0F70E5Dd", + 5: "0x829fdCDf6Be747FEA37518fBd83dF70EE371fCf2", + 1337: AddressZero, +} as Record + export class MultiAppStaking { private _staking: IStaking private _multicall: IMulticall public readonly randomBeacon: IApplication public readonly ecdsa: IApplication + public readonly pre: IPREApplication constructor( staking: IStaking, multicall: IMulticall, config: EthereumConfig ) { + const preAddress = PRE_ADDRESSESS[config.chainId] + if (!preAddress) { + throw new Error("Unsupported chain id") + } + this._staking = staking this._multicall = multicall this.randomBeacon = new Application(this._staking, this._multicall, { @@ -42,6 +60,11 @@ export class MultiAppStaking { abi: WalletRegistry.abi, ...config, }) + this.pre = new PREApplication(this._multicall, { + address: preAddress, + abi: SimplePREApplicationABI, + ...config, + }) } async getSupportedAppsAuthParameters(): Promise { @@ -85,14 +108,21 @@ export class MultiAppStaking { method: "stakingProviderToOperator", args: [stakingProvider], }, + { + interface: this.pre.contract.interface, + address: this.pre.address, + method: "getOperatorFromStakingProvider", + args: [stakingProvider], + }, ] - const [mappedOperatorTbtc, mappedOperatorRandomBeacon] = + const [mappedOperatorTbtc, mappedOperatorRandomBeacon, mappedOperatorPre] = await this._multicall.aggregate(calls) return { tbtc: mappedOperatorTbtc.toString(), randomBeacon: mappedOperatorRandomBeacon.toString(), + pre: mappedOperatorPre.toString(), } } } diff --git a/src/threshold-ts/utils/abi/SimplePreApplication.json b/src/threshold-ts/utils/abi/SimplePreApplication.json new file mode 100644 index 000000000..2e2dc82ce --- /dev/null +++ b/src/threshold-ts/utils/abi/SimplePreApplication.json @@ -0,0 +1,241 @@ +[ + { + "inputs": [ + { + "internalType": "contract IStaking", + "name": "_tStaking", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_minAuthorization", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_minOperatorSeconds", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "startTimestamp", + "type": "uint256" + } + ], + "name": "OperatorBonded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "OperatorConfirmed", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_stakingProvider", + "type": "address" + } + ], + "name": "authorizedStake", + "outputs": [{ "internalType": "uint96", "name": "", "type": "uint96" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_stakingProvider", + "type": "address" + }, + { "internalType": "address", "name": "_operator", "type": "address" } + ], + "name": "bondOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "confirmOperatorAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_startIndex", "type": "uint256" }, + { + "internalType": "uint256", + "name": "_maxStakingProviders", + "type": "uint256" + } + ], + "name": "getActiveStakingProviders", + "outputs": [ + { + "internalType": "uint256", + "name": "allAuthorizedTokens", + "type": "uint256" + }, + { + "internalType": "uint256[2][]", + "name": "activeStakingProviders", + "type": "uint256[2][]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_stakingProvider", + "type": "address" + } + ], + "name": "getBeneficiary", + "outputs": [ + { + "internalType": "address payable", + "name": "beneficiary", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_stakingProvider", + "type": "address" + } + ], + "name": "getOperatorFromStakingProvider", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStakingProvidersLength", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_stakingProvider", + "type": "address" + } + ], + "name": "isAuthorized", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_operator", "type": "address" } + ], + "name": "isOperatorConfirmed", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minAuthorization", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minOperatorSeconds", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_operator", "type": "address" } + ], + "name": "stakingProviderFromOperator", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "stakingProviderInfo", + "outputs": [ + { "internalType": "address", "name": "operator", "type": "address" }, + { "internalType": "bool", "name": "operatorConfirmed", "type": "bool" }, + { + "internalType": "uint256", + "name": "operatorStartTimestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "stakingProviders", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tStaking", + "outputs": [ + { "internalType": "contract IStaking", "name": "", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + } +]