diff --git a/src/abi/OWR.ts b/src/abi/OWR.ts index 351d5ee..9351b99 100644 --- a/src/abi/OWR.ts +++ b/src/abi/OWR.ts @@ -128,3 +128,175 @@ export const OWRFactoryContract = { }, ], }; + +export const OWRContract = { + abi: [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { inputs: [], name: 'InvalidDistribution_TooLarge', type: 'error' }, + { + inputs: [], + name: 'InvalidTokenRecovery_InvalidRecipient', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'principalPayout', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'rewardPayout', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'pullFlowFlag', + type: 'uint256', + }, + ], + name: 'DistributeFunds', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ReceiveETH', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'recoveryAddressToken', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'RecoverNonOWRecipientFunds', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Withdrawal', + type: 'event', + }, + { + inputs: [], + name: 'claimedPrincipalFunds', + outputs: [{ internalType: 'uint128', name: '', type: 'uint128' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'distributeFunds', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'distributeFundsPull', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'fundsPendingWithdrawal', + outputs: [{ internalType: 'uint128', name: '', type: 'uint128' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'account', type: 'address' }], + name: 'getPullBalance', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTranches', + outputs: [ + { + internalType: 'address', + name: 'principalRecipient', + type: 'address', + }, + { internalType: 'address', name: 'rewardRecipient', type: 'address' }, + { + internalType: 'uint256', + name: 'amountOfPrincipalStake', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'nonOWRToken', type: 'address' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + ], + name: 'recoverFunds', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'recoveryAddress', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'account', type: 'address' }], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], +}; diff --git a/src/index.ts b/src/index.ts index 001fcee..37e0d6a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,6 +28,8 @@ import { type OperatorPayload, type TotalSplitPayload, type ClusterValidator, + type ETH_ADDRESS, + type OWRTranches, } from './types.js'; import { clusterConfigOrDefinitionHash } from './verification/common.js'; import { validatePayload } from './ajv.js'; @@ -42,6 +44,7 @@ import { formatSplitRecipients, handleDeployOWRAndSplitter, predictSplitterAddress, + getOWRTranches, } from './splitHelpers.js'; import { isContractAvailable } from './utils.js'; export * from './types.js'; @@ -338,6 +341,26 @@ export class Client extends Base { }; } + /** + * Read OWR Tranches. + * + * @remarks + * **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed + * and not pushed to version control. + * + * @param {ETH_ADDRESS} owrAddress - Address of the Deployed OWR Contract + * @returns {Promise} owr tranch information about principal and reward reciepient, as well as the principal amount + * + */ + async getOWRTranches(owrAddress: ETH_ADDRESS): Promise { + if (!this.signer) { + throw new Error('Signer is required in getOWRTranches'); + } + + const signer = this.signer; + return await getOWRTranches({ owrAddress, signer }); + } + /** * Creates a cluster definition which contains cluster configuration. * @param {ClusterPayload} newCluster - The new unique cluster. diff --git a/src/splitHelpers.ts b/src/splitHelpers.ts index 2f7548d..598c7ec 100644 --- a/src/splitHelpers.ts +++ b/src/splitHelpers.ts @@ -1,4 +1,5 @@ import { + type OWRTranches, type ClusterValidator, type ETH_ADDRESS, type SplitRecipient, @@ -10,7 +11,7 @@ import { ZeroAddress, type Signer, } from 'ethers'; -import { OWRFactoryContract } from './abi/OWR'; +import { OWRContract, OWRFactoryContract } from './abi/OWR'; import { splitMainEthereumAbi } from './abi/SplitMain'; import { MultiCallContract } from './abi/Multicall'; import { CHAIN_CONFIGURATION } from './constants'; @@ -237,6 +238,7 @@ export const deploySplitterContract = async ({ throw e; } }; + export const deploySplitterAndOWRContracts = async ({ owrArgs, splitterArgs, @@ -297,6 +299,23 @@ export const deploySplitterAndOWRContracts = async ({ } }; +export const getOWRTranches = async ({ + owrAddress, + signer, +}: { + owrAddress: ETH_ADDRESS; + signer: Signer; +}): Promise => { + const owrContract = new Contract(owrAddress, OWRContract.abi, signer); + const res = await owrContract.getTranches(); + + return { + principalRecipient: res.principalRecipient, + rewardRecipient: res.rewardRecipient, + amountOfPrincipalStake: res.amountOfPrincipalStake, + }; +}; + export const multicall = async ( calls: Call[], signer: Signer, diff --git a/src/types.ts b/src/types.ts index 8e981b8..c318a43 100644 --- a/src/types.ts +++ b/src/types.ts @@ -159,6 +159,20 @@ export interface RewardsSplitPayload extends TotalSplitPayload { recoveryAddress?: string; } +/** + * OWR Tranches + */ +export type OWRTranches = { + /** Address that will reclaim validator principal after exit. */ + principalRecipient: ETH_ADDRESS; + + /** Address that will reclaim validator rewards during operation. */ + rewardRecipient: ETH_ADDRESS; + + /** Amount of principal staked. */ + amountOfPrincipalStake: number; +}; + /** * Unsigned DV Builder Registration Message */ diff --git a/test/sdk-package-test/cluster.test.ts b/test/sdk-package-test/cluster.test.ts index 4c81088..8c4a443 100755 --- a/test/sdk-package-test/cluster.test.ts +++ b/test/sdk-package-test/cluster.test.ts @@ -133,46 +133,73 @@ describe('Cluster Definition', () => { }); it('should deploy OWR and Splitter', async () => { - const signerAddress = await randomSigner.getAddress(); + const secondRandomSignerAddress = await secondRandomSigner.getAddress(); // new splitter const { withdrawal_address, fee_recipient_address } = - await client.createObolRewardsSplit({ + await client.createObolTotalSplit({ splitRecipients: [ - { account: signerAddress, percentAllocation: 39 }, + { account: secondRandomSignerAddress, percentAllocation: 39.9 }, { account: '0xf6fF1a7A14D01e86a175bA958d3B6C75f2213966', percentAllocation: 60, }, ], - principalRecipient: '0xf6fF1a7A14D01e86a175bA958d3B6C75f2213966', - etherAmount: 2, - recoveryAddress: '0xf6fF1a7A14D01e86a175bA958d3B6C75f2213966', }); // same splitter const contractsWithSameFeeRecipientAddress = - await client.createObolRewardsSplit({ + await client.createObolTotalSplit({ splitRecipients: [ - { account: signerAddress, percentAllocation: 39 }, + { account: secondRandomSignerAddress, percentAllocation: 39.9 }, { account: '0xf6fF1a7A14D01e86a175bA958d3B6C75f2213966', percentAllocation: 60, }, ], - principalRecipient: '0xf6fF1a7A14D01e86a175bA958d3B6C75f2213966', - etherAmount: 2, }); expect(withdrawal_address.length).toEqual(42); - expect(fee_recipient_address.length).toEqual(42); - expect( - contractsWithSameFeeRecipientAddress.withdrawal_address.length, - ).toEqual(42); + + expect(fee_recipient_address.toLowerCase()).toEqual( + withdrawal_address.toLowerCase(), + ); + expect(fee_recipient_address.toLowerCase()).toEqual( contractsWithSameFeeRecipientAddress.fee_recipient_address.toLowerCase(), ); }); + it('should deploy OWR and splitter and get tranches', async () => { + const secondRandomSignerAddress = await secondRandomSigner.getAddress(); + const principalRecipient = '0xf6fF1a7A14D01e86a175bA958d3B6C75f2213966'; + + // new splitter + const { withdrawal_address, fee_recipient_address } = + await client.createObolRewardsSplit({ + splitRecipients: [ + { account: secondRandomSignerAddress, percentAllocation: 39 }, + { + account: principalRecipient, + percentAllocation: 60, + }, + ], + principalRecipient: principalRecipient, + etherAmount: 2, + distributorFee: 2, + controllerAddress: principalRecipient, + }); + + const res = await client.getOWRTranches(withdrawal_address); + + expect(res.principalRecipient.toLowerCase()).toEqual( + principalRecipient.toLowerCase(), + ); + expect(res.rewardRecipient.toLowerCase()).toEqual( + fee_recipient_address.toLowerCase(), + ); + expect(res.amountOfPrincipalStake).toEqual(BigInt(2000000000000000000)); + }); + it('should deploy OWR and Splitter with a controller address and a distributorFee', async () => { const signerAddress = await randomSigner.getAddress(); // new splitter diff --git a/test/sdk-package-test/yarn.lock b/test/sdk-package-test/yarn.lock index 8f77d98..0726cfa 100644 --- a/test/sdk-package-test/yarn.lock +++ b/test/sdk-package-test/yarn.lock @@ -1683,9 +1683,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001663: - version "1.0.30001667" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz#99fc5ea0d9c6e96897a104a8352604378377f949" - integrity sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw== + version "1.0.30001669" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz#fda8f1d29a8bfdc42de0c170d7f34a9cf19ed7a3" + integrity sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w== chalk@^2.4.2: version "2.4.2" @@ -1986,9 +1986,9 @@ dotenv@*, dotenv@^16.3.1: integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== electron-to-chromium@^1.5.28: - version "1.5.35" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.35.tgz#1d38d386186c72b1fa6e74c3a7de5f888b503100" - integrity sha512-hOSRInrIDm0Brzp4IHW2F/VM+638qOL2CzE0DgpnGzKW27C95IqqeqgKz/hxHGnvPxvQGpHUGD5qRVC9EZY2+A== + version "1.5.39" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.39.tgz#5cbe5200b43dff7b7c2bcb6bdacf65d514c76bb2" + integrity sha512-4xkpSR6CjuiaNyvwiWDI85N9AxsvbPawB8xc7yzLPonYTuP19BVgYweKyUMFtHEZgIcHWMt1ks5Cqx2m+6/Grg== elliptic@^6.5.4: version "6.5.7" @@ -2356,9 +2356,9 @@ fast-safe-stringify@^2.1.1: integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== fast-uri@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.2.tgz#d78b298cf70fd3b752fd951175a3da6a7b48f024" - integrity sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row== + version "3.0.3" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241" + integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== fastq@^1.6.0: version "1.17.1" @@ -3835,9 +3835,9 @@ murmurhash3js-revisited@^3.0.0: integrity sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g== nan@^2.14.2: - version "2.21.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.21.0.tgz#203ab765a02e6676c8cb92e1cad9503e7976d55b" - integrity sha512-MCpOGmdWvAOMi4RWnpxS5G24l7dVMtdSHtV87I3ltjaLdFOTO74HVJ+DfYiAXjxGKsYR/UCmm1rBwhMN7KqS1A== + version "2.22.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.0.tgz#31bc433fc33213c97bad36404bb68063de604de3" + integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw== native-fetch@^3.0.0: version "3.0.0"