diff --git a/contracts/ethereum/helpers/upkeep.ts b/contracts/ethereum/helpers/upkeep.ts index 67f5d14fe..30cdbcee5 100644 --- a/contracts/ethereum/helpers/upkeep.ts +++ b/contracts/ethereum/helpers/upkeep.ts @@ -45,7 +45,7 @@ export async function fulfillReport({ const requestIds = (await upkeep.queryFilter(upkeep.filters.RequestSent())).slice(-2).map((event) => event.args.id) const balancesRequestId = requestIds[0] - const balancesResponseBytes = ethers.utils.defaultAbiCoder.encode( + const balancesResponse = ethers.utils.defaultAbiCoder.encode( ['uint128', 'uint128'], [ethers.utils.parseEther(activeBalance.toString()), ethers.utils.parseEther(sweptBalance.toString())] ) @@ -54,11 +54,11 @@ export async function fulfillReport({ keeper, functionsBillingRegistry, requestId: balancesRequestId, - responseBytes: balancesResponseBytes + response: balancesResponse }) const detailsRequestId = requestIds[1] - const detailsResponseBytes = ethers.utils.defaultAbiCoder.encode( + const detailsResponse = ethers.utils.defaultAbiCoder.encode( ['uint32', 'uint32', 'uint32', 'uint32[5]'], [activatedDeposits, forcedExits, completedExits, compoundablePoolIds] ) @@ -67,7 +67,7 @@ export async function fulfillReport({ keeper, functionsBillingRegistry, requestId: detailsRequestId, - responseBytes: detailsResponseBytes + response: detailsResponse }) } @@ -75,12 +75,12 @@ export async function fulfillFunctionsRequest({ keeper, functionsBillingRegistry, requestId, - responseBytes + response }: { keeper: SignerWithAddress, functionsBillingRegistry: FunctionsBillingRegistry, requestId: string, - responseBytes: string + response: string }) { const dummyTransmitter = keeper.address const dummySigners = Array(31).fill(dummyTransmitter) @@ -89,7 +89,7 @@ export async function fulfillFunctionsRequest({ const fulfillAndBill = await functionsBillingRegistry.connect(keeper).fulfillAndBill( requestId, - responseBytes, + response, '0x', dummyTransmitter, dummySigners, diff --git a/contracts/ethereum/scripts/dev.ts b/contracts/ethereum/scripts/dev.ts index e9349985d..3c714f60e 100644 --- a/contracts/ethereum/scripts/dev.ts +++ b/contracts/ethereum/scripts/dev.ts @@ -114,10 +114,10 @@ void async function () { await result.wait() } - const secrets = '0x' // Parse requestConfig.secrets and encrypt if necessary - const requestCBOR = await upkeep.generateRequest(requestConfig.source, secrets, requestConfig.args) + const requestSource = requestConfig.source + const requestArgs = requestConfig.args const fulfillGasLimit = 300000 - const setRequest = await manager.setFunctionsRequest(requestCBOR, fulfillGasLimit) + const setRequest = await manager.setFunctionsRequest(requestSource, requestArgs, fulfillGasLimit) await setRequest.wait() await functionsBillingRegistry.setAuthorizedSenders([keeper.address, manager.address, upkeep.address, functionsOracle.address]) diff --git a/contracts/ethereum/src/v1/CasimirManager.sol b/contracts/ethereum/src/v1/CasimirManager.sol index 4ec84c02d..6d2061424 100644 --- a/contracts/ethereum/src/v1/CasimirManager.sol +++ b/contracts/ethereum/src/v1/CasimirManager.sol @@ -251,7 +251,6 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { swapFactory = IUniswapV3Factory(swapFactoryAddress); swapRouter = ISwapRouter(swapRouterAddress); tokenAddresses[Token.WETH] = wethTokenAddress; - registry = new CasimirRegistry(ssvNetworkViewsAddress); upkeep = new CasimirUpkeep(functionsOracleAddress); } @@ -543,7 +542,6 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { user.stakeRatioSum0 = stakeRatioSum; user.stake0 -= amount; - if (amount <= getWithdrawableBalance()) { if (amount <= exitedBalance) { exitedBalance -= amount; @@ -563,7 +561,6 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { ); requestedWithdrawalBalance += amount; requestedWithdrawals++; - uint256 coveredExitBalance = requestedExits * POOL_CAPACITY; if (requestedWithdrawalBalance > coveredExitBalance) { uint256 exitsRequired = (requestedWithdrawalBalance - @@ -601,7 +598,6 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { requestedWithdrawalQueue.remove(0); withdrawalAmount += withdrawal.amount; withdrawalCount++; - fulfillWithdrawal(withdrawal.user, withdrawal.amount); } if (withdrawalAmount <= exitedBalance) { @@ -644,7 +640,6 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { uint32 poolId = readyPoolIds[0]; readyPoolIds.remove(0); pendingPoolIds.push(poolId); - poolAddresses[poolId] = address( new CasimirPool( address(registry), @@ -653,32 +648,44 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { operatorIds ) ); - bytes memory computedWithdrawalCredentials = abi.encodePacked( bytes1(uint8(1)), bytes11(0), poolAddresses[poolId] ); - require( keccak256(computedWithdrawalCredentials) == keccak256(withdrawalCredentials), "Invalid withdrawal credentials" ); - registerPool( - poolId, - depositDataRoot, - publicKey, - signature, - withdrawalCredentials, - operatorIds, - shares, - cluster, - feeAmount, - minimumTokenAmount, - processed - ); + { + for (uint256 i = 0; i < operatorIds.length; i++) { + registry.addOperatorPool(operatorIds[i], poolId); + } + beaconDeposit.deposit{value: POOL_CAPACITY}( + publicKey, + withdrawalCredentials, + signature, + depositDataRoot + ); + uint256 ssvAmount = retrieveFees( + feeAmount, + minimumTokenAmount, + tokenAddresses[Token.SSV], + processed + ); + ssvToken.approve(address(ssvNetwork), ssvAmount); + ssvNetwork.registerValidator( + publicKey, + operatorIds, + shares, + ssvAmount, + cluster + ); + + emit PoolRegistered(poolId); + } emit DepositInitiated(poolId); } @@ -781,7 +788,6 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { ); stakedPoolIds.remove(poolIndex); - if (poolDetails.status == ICasimirPool.PoolStatus.EXITING_REQUESTED) { requestedExits--; } else if ( @@ -789,7 +795,6 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { ) { forcedExits--; } - pool.setStatus(ICasimirPool.PoolStatus.WITHDRAWN); pool.withdrawBalance(blamePercents); ssvNetwork.removeValidator(poolDetails.publicKey, poolDetails.operatorIds, cluster); @@ -919,16 +924,26 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { /** * Set a new Chainlink functions request - * @param newRequestCBOR The new Chainlink functions request CBOR + * @param newRequestSource JavaScript source code + * @param newRequestArgs List of arguments accessible from within the source code * @param newFulfillGasLimit The new Chainlink functions fulfill gas limit */ function setFunctionsRequest( - bytes calldata newRequestCBOR, + string calldata newRequestSource, + string[] calldata newRequestArgs, uint32 newFulfillGasLimit ) external onlyOwner { - upkeep.setRequest(newRequestCBOR, newFulfillGasLimit); + upkeep.setRequest( + newRequestSource, + newRequestArgs, + newFulfillGasLimit + ); - emit FunctionsRequestSet(newRequestCBOR, newFulfillGasLimit); + emit FunctionsRequestSet( + newRequestSource, + newRequestArgs, + newFulfillGasLimit + ); } /** @@ -1112,6 +1127,7 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { block.timestamp >= user.actionPeriodTimestamp + ACTION_PERIOD, "Action period maximum reached" ); + if (block.timestamp >= user.actionPeriodTimestamp + ACTION_PERIOD) { user.actionPeriodTimestamp = block.timestamp; user.actionCount = 1; @@ -1120,63 +1136,6 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { } } - /** - * @dev Register a pool with Beacon and SSV - * @param poolId The pool ID - * @param depositDataRoot The deposit data root - * @param publicKey The validator public key - * @param signature The signature - * @param withdrawalCredentials The withdrawal credentials - * @param operatorIds The operator IDs - * @param shares The operator shares - * @param cluster The SSV cluster snapshot - * @param feeAmount The fee amount to deposit - * @param minimumTokenAmount The minimum SSV token amount out after processing fees - * @param processed Whether the fee amount is already processed - */ - function registerPool( - uint32 poolId, - bytes32 depositDataRoot, - bytes memory publicKey, - bytes memory signature, - bytes memory withdrawalCredentials, - uint64[] memory operatorIds, - bytes memory shares, - ISSVNetworkCore.Cluster memory cluster, - uint256 feeAmount, - uint256 minimumTokenAmount, - bool processed - ) private { - for (uint256 i = 0; i < operatorIds.length; i++) { - registry.addOperatorPool(operatorIds[i], poolId); - } - - beaconDeposit.deposit{value: POOL_CAPACITY}( - publicKey, - withdrawalCredentials, - signature, - depositDataRoot - ); - - uint256 ssvAmount = retrieveFees( - feeAmount, - minimumTokenAmount, - tokenAddresses[Token.SSV], - processed - ); - ssvToken.approve(address(ssvNetwork), ssvAmount); - - ssvNetwork.registerValidator( - publicKey, - operatorIds, - shares, - ssvAmount, - cluster - ); - - emit PoolRegistered(poolId); - } - /** * @notice Request a given count of staked pool exits * @param count The number of exits to request @@ -1193,7 +1152,6 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { ) { count--; index++; - pool.setStatus(ICasimirPool.PoolStatus.EXITING_REQUESTED); requestedExits++; @@ -1241,7 +1199,6 @@ contract CasimirManager is ICasimirManager, Ownable, ReentrancyGuard { address(swapRouter), wethToken.balanceOf(address(this)) ); - IUniswapV3PoolState swapPool = IUniswapV3PoolState( swapFactory.getPool( tokenAddresses[Token.WETH], diff --git a/contracts/ethereum/src/v1/CasimirPool.sol b/contracts/ethereum/src/v1/CasimirPool.sol index d55b2a3c5..fcf17b75f 100644 --- a/contracts/ethereum/src/v1/CasimirPool.sol +++ b/contracts/ethereum/src/v1/CasimirPool.sol @@ -27,13 +27,13 @@ contract CasimirPool is ICasimirPool, Ownable, ReentrancyGuard { ICasimirManager private immutable manager; /** Registry contract */ ICasimirRegistry private immutable registry; + /** Pool ID */ + uint32 private immutable id; /*********/ /* State */ /*********/ - /** Pool ID */ - uint32 private immutable id; /** Validator public key */ bytes private publicKey; /** Operator IDs */ diff --git a/contracts/ethereum/src/v1/CasimirRegistry.sol b/contracts/ethereum/src/v1/CasimirRegistry.sol index f5998b202..431f191e8 100644 --- a/contracts/ethereum/src/v1/CasimirRegistry.sol +++ b/contracts/ethereum/src/v1/CasimirRegistry.sol @@ -212,12 +212,10 @@ contract CasimirRegistry is ICasimirRegistry, Ownable { operatorPools[operatorId][poolId] = false; operator.poolCount -= 1; - if (operator.poolCount == 0 && operator.resharing) { operator.active = false; operator.resharing = false; } - if (blameAmount > 0) { operator.collateral -= blameAmount; manager.depositRecoveredBalance{value: blameAmount}(poolId); diff --git a/contracts/ethereum/src/v1/CasimirUpkeep.sol b/contracts/ethereum/src/v1/CasimirUpkeep.sol index 2820d4e3f..a53518170 100644 --- a/contracts/ethereum/src/v1/CasimirUpkeep.sol +++ b/contracts/ethereum/src/v1/CasimirUpkeep.sol @@ -4,8 +4,9 @@ pragma solidity 0.8.18; import "./interfaces/ICasimirUpkeep.sol"; import "./interfaces/ICasimirManager.sol"; import "./vendor/FunctionsClient.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; /** * @title Upkeep contract that automates and handles reports @@ -22,7 +23,7 @@ contract CasimirUpkeep is ICasimirUpkeep, FunctionsClient, Ownable { /* Constants */ /*************/ - /** Oracle heartbeat */ + /** Report-to-report heartbeat duration */ uint256 private constant REPORT_HEARTBEAT = 1 days; /*************/ @@ -36,6 +37,8 @@ contract CasimirUpkeep is ICasimirUpkeep, FunctionsClient, Ownable { /* State */ /*********/ + /** Previous report timestamp */ + uint256 private previousReportTimestamp; /** Current report status */ ReportStatus private reportStatus; /** Current report period */ @@ -66,10 +69,12 @@ contract CasimirUpkeep is ICasimirUpkeep, FunctionsClient, Ownable { mapping(bytes32 => RequestType) private reportRequests; /** Current report response error */ bytes private reportResponseError; - /** Binary request source code */ - bytes private requestCBOR; + /** Request source */ + string requestSource; + /** Request arguments */ + string[] public requestArgs; /** Fulfillment gas limit */ - uint32 private fulfillGasLimit; + uint32 public fulfillGasLimit; /***************/ /* Constructor */ @@ -92,17 +97,24 @@ contract CasimirUpkeep is ICasimirUpkeep, FunctionsClient, Ownable { /** * Set a new Chainlink functions request - * @param newRequestCBOR The new Chainlink functions request CBOR + * @param newRequestSource JavaScript source code + * @param newRequestArgs List of arguments accessible from within the source code * @param newFulfillGasLimit The new Chainlink functions fulfill gas limit */ function setRequest( - bytes calldata newRequestCBOR, + string calldata newRequestSource, + string[] calldata newRequestArgs, uint32 newFulfillGasLimit ) external onlyOwner { - requestCBOR = newRequestCBOR; + requestSource = newRequestSource; + requestArgs = newRequestArgs; fulfillGasLimit = newFulfillGasLimit; - emit RequestSet(newRequestCBOR, newFulfillGasLimit); + emit RequestSet( + newRequestSource, + newRequestArgs, + newFulfillGasLimit + ); } /** @@ -113,19 +125,23 @@ contract CasimirUpkeep is ICasimirUpkeep, FunctionsClient, Ownable { require(upkeepNeeded, "Upkeep not needed"); if (reportStatus == ReportStatus.FINALIZED) { + previousReportTimestamp = reportTimestamp; reportStatus = ReportStatus.REQUESTING; reportRequestBlock = block.number; reportTimestamp = block.timestamp; reportPeriod = manager.reportPeriod(); - uint64 functionsId = manager.functionsId(); - reportRemainingRequests = 2; - for (uint256 i = 0; i < reportRemainingRequests; i++) { - bytes32 requestId = s_oracle.sendRequest(functionsId, requestCBOR, fulfillGasLimit); - s_pendingRequests[requestId] = s_oracle.getRegistry(); - reportRequests[requestId] = RequestType(i + 1); - - emit RequestSent(requestId); - } + Functions.Request memory currentRequest; + currentRequest.initializeRequest( + Functions.Location.Inline, + Functions.CodeLanguage.JavaScript, + requestSource + ); + string[] memory currentRequestArgs = requestArgs; + currentRequestArgs[6] = Strings.toString(previousReportTimestamp); + currentRequestArgs[7] = Strings.toString(reportTimestamp); + currentRequestArgs[8] = Strings.toString(reportRequestBlock); + sendReportRequest(currentRequest, currentRequestArgs, RequestType.BALANCES); + sendReportRequest(currentRequest, currentRequestArgs, RequestType.DETAILS); } else { if ( manager.requestedWithdrawalBalance() > 0 && @@ -174,32 +190,6 @@ contract CasimirUpkeep is ICasimirUpkeep, FunctionsClient, Ownable { emit OracleAddressSet(newOracleAddress); } - /** - * @notice Generate a new Functions.Request(off-chain, saving gas) - * @param source JavaScript source code - * @param secrets Encrypted secrets payload - * @param args List of arguments accessible from within the source code - */ - function generateRequest( - string calldata source, - bytes calldata secrets, - string[] calldata args - ) external pure returns (bytes memory) { - Functions.Request memory req; - req.initializeRequest( - Functions.Location.Inline, - Functions.CodeLanguage.JavaScript, - source - ); - if (secrets.length > 0) { - req.addRemoteSecrets(secrets); - } - if (args.length > 0) { - req.addArgs(args); - } - return req.encodeCBOR(); - } - /** * @notice Check if the upkeep is needed * @return upkeepNeeded True if the upkeep is needed @@ -210,7 +200,7 @@ contract CasimirUpkeep is ICasimirUpkeep, FunctionsClient, Ownable { public view override - returns (bool upkeepNeeded, bytes memory) + returns (bool upkeepNeeded, bytes memory checkData) { if (reportStatus == ReportStatus.FINALIZED) { bool checkActive = manager.getPendingPoolIds().length + manager.getStakedPoolIds().length > 0; @@ -220,25 +210,26 @@ contract CasimirUpkeep is ICasimirUpkeep, FunctionsClient, Ownable { bool finalizeReport = reportCompletedExits == manager.finalizableCompletedExits(); upkeepNeeded = finalizeReport; } + return (upkeepNeeded, checkData); } /** * @notice Callback that is invoked once the DON has resolved the request or hit an error * @param requestId The request ID, returned by sendRequest() - * @param response Aggregated response from the user code - * @param _error Aggregated error from the user code or from the sweptStake pipeline + * @param response Aggregated response from the DON + * @param executionError Aggregated error from the code execution * Either response or error parameter will be set, but never both */ function fulfillRequest( bytes32 requestId, bytes memory response, - bytes memory _error + bytes memory executionError ) internal override { RequestType requestType = reportRequests[requestId]; require(requestType != RequestType.NONE, "Invalid request ID"); - reportResponseError = _error; - if (_error.length == 0) { + reportResponseError = executionError; + if (executionError.length == 0) { delete reportRequests[requestId]; reportRemainingRequests--; if (requestType == RequestType.BALANCES) { @@ -273,6 +264,24 @@ contract CasimirUpkeep is ICasimirUpkeep, FunctionsClient, Ownable { } } - emit OCRResponse(requestId, response, _error); + emit OCRResponse(requestId, response, executionError); + } + + /** + * @notice Send a report request + * @param currentRequest The Chainlink functions request + * @param currentRequestArgs The Chainlink functions request arguments + * @param currentRequestType The Chainlink functions request type + */ + function sendReportRequest( + Functions.Request memory currentRequest, + string[] memory currentRequestArgs, + RequestType currentRequestType + ) private { + currentRequestArgs[9] = Strings.toString(uint256(currentRequestType)); + currentRequest.addArgs(currentRequestArgs); + bytes32 requestId = sendRequest(currentRequest, manager.functionsId(), fulfillGasLimit); + reportRequests[requestId] = currentRequestType; + reportRemainingRequests++; } } diff --git a/contracts/ethereum/src/v1/interfaces/ICasimirManager.sol b/contracts/ethereum/src/v1/interfaces/ICasimirManager.sol index 0b9d011db..15c711724 100644 --- a/contracts/ethereum/src/v1/interfaces/ICasimirManager.sol +++ b/contracts/ethereum/src/v1/interfaces/ICasimirManager.sol @@ -40,7 +40,7 @@ interface ICasimirManager { event DepositInitiated(uint32 indexed poolId); event DepositActivated(uint32 indexed poolId); event ForcedExitsReported(uint32[] poolIds); - event FunctionsRequestSet(bytes newRequestCBOR, uint32 newFulfillGasLimit); + event FunctionsRequestSet(string newRequestSource, string[] newRequestArgs, uint32 newFulfillGasLimit); event FunctionsOracleAddressSet(address newFunctionsOracleAddress); event LINKBalanceWithdrawn(uint256 amount); event ResharesRequested(uint64 indexed operatorId); @@ -140,7 +140,11 @@ interface ICasimirManager { ) external; function withdrawLINKBalance(uint256 amount) external; function withdrawSSVBalance(uint256 amount) external; - function setFunctionsRequest(bytes calldata newRequestCBOR, uint32 newFulfillGasLimit) external; + function setFunctionsRequest( + string calldata newRequestSource, + string[] calldata newRequestArgs, + uint32 newFulfillGasLimit + ) external; function setFunctionsOracleAddress(address newOracleAddress) external; function cancelFunctions() external; function cancelUpkeep() external; diff --git a/contracts/ethereum/src/v1/interfaces/ICasimirUpkeep.sol b/contracts/ethereum/src/v1/interfaces/ICasimirUpkeep.sol index b8a3d5891..f3d1ddafc 100644 --- a/contracts/ethereum/src/v1/interfaces/ICasimirUpkeep.sol +++ b/contracts/ethereum/src/v1/interfaces/ICasimirUpkeep.sol @@ -24,7 +24,7 @@ interface ICasimirUpkeep is AutomationCompatibleInterface { /**********/ event OCRResponse(bytes32 indexed requestId, bytes result, bytes err); - event RequestSet(bytes newRequestCBOR, uint32 newFulfillGasLimit); + event RequestSet(string newRequestSource, string[] newRequestArgs, uint32 newFulfillGasLimit); event OracleAddressSet(address newOracleAddress); event UpkeepPerformed(ReportStatus indexed status); @@ -33,7 +33,11 @@ interface ICasimirUpkeep is AutomationCompatibleInterface { /*************/ function performUpkeep(bytes calldata performData) external; - function setRequest(bytes calldata newRequestCBOR, uint32 newFulfillGasLimit) external; + function setRequest( + string calldata newRequestSource, + string[] calldata newRequestArgs, + uint32 newFulfillGasLimit + ) external; function setOracleAddress(address newOracleAddress) external; /***********/ diff --git a/contracts/ethereum/test/fixtures/shared.ts b/contracts/ethereum/test/fixtures/shared.ts index 5fad42077..0542a1c7d 100644 --- a/contracts/ethereum/test/fixtures/shared.ts +++ b/contracts/ethereum/test/fixtures/shared.ts @@ -103,10 +103,10 @@ export async function deploymentFixture() { await result.wait() } - const secrets = '0x' // Parse requestConfig.secrets and encrypt if necessary - const requestCBOR = await upkeep.generateRequest(requestConfig.source, secrets, requestConfig.args) + const requestSource = requestConfig.source + const requestArgs = requestConfig.args const fulfillGasLimit = 300000 - const setRequest = await manager.setFunctionsRequest(requestCBOR, fulfillGasLimit) + const setRequest = await manager.setFunctionsRequest(requestSource, requestArgs, fulfillGasLimit) await setRequest.wait() await functionsBillingRegistry.setAuthorizedSenders([keeper.address, manager.address, upkeep.address, functionsOracle.address]) diff --git a/services/functions/.gitignore b/services/functions/.gitignore new file mode 100644 index 000000000..ae928f8c6 --- /dev/null +++ b/services/functions/.gitignore @@ -0,0 +1 @@ +.env.enc \ No newline at end of file diff --git a/services/functions/API-request-source.js b/services/functions/API-request-source.js index 4bbd4fd6d..fbeb3cbe3 100644 --- a/services/functions/API-request-source.js +++ b/services/functions/API-request-source.js @@ -1,43 +1,27 @@ -/**********************/ -/** MOCK BEACON DATA **/ -const mockValidatorPublicKeys = [ - '0x88e1b777156a0945851d2940d604727fce299daaaf12aa6c04410c3e31887ab018f6a748017aa8717fbb7f242624afcf', - '0x96c9cd76264cdfd84d6c3c704101db435c150cbfe088c031e1bef740f8f7de24fceccdd755d1d2e091ccb6e58fb93db4', - '0x832e23566d61f2b141e1ab57e2a4362d228de7bb53acb096f0ea6d500d3490b323b41ce3ae2104772ba243ae48cdccfd', - '0x8b65cc087ed99e559b4e1be14776a54e02b458cdd79d792394186539cdd53ea937647cd4aba333a470ba9f6393c6e612' -] -const mockPreviousReportSlot = 6055000 -const mockReportSlot = 6055335 -/**********************/ - const genesisTimestamp = args[0] -const previousReportTimestamp = args[1] -const reportTimestamp = args[2] -const reportBlockNumber = '0x' + parseInt(args[3]).toString(16) -const requestType = args[4] -const viewsAddress = args[5] -const getCompoundablePoolIdsSignature = args[6] -const getDepositedPoolCountSignature = args[7] -const getSweptBalanceSignature = args[8] -const getValidatorPublicKeysSignature = args[9] -const ethereumUrl = secrets.ethereumRpcUrl || 'http://localhost:8545' -const ethereumBeaconUrl = secrets.ethereumBeaconRpcUrl || 'http://localhost:5052' -const previousReportSlot = mockPreviousReportSlot // Math.floor((previousReportTimestamp - genesisTimestamp) / 12) +const viewsAddress = args[1] +const getCompoundablePoolIdsSignature = args[2] +const getDepositedPoolCountSignature = args[3] +const getSweptBalanceSignature = args[4] +const getValidatorPublicKeysSignature = args[5] +const previousReportTimestamp = args[6] +const reportTimestamp = args[7] +const reportBlockNumber = '0x' + parseInt(args[8]).toString(16) +const requestType = args[9] +const ethereumUrl = secrets.ethereumRpcUrl ?? 'http://localhost:8546' +const ethereumBeaconUrl = secrets.ethereumBeaconRpcUrl ?? 'http://localhost:5052' +const previousReportSlot = Math.floor((previousReportTimestamp - genesisTimestamp) / 12) const previousReportEpoch = Math.floor(previousReportSlot / 32) -const reportSlot = mockReportSlot // Math.floor((reportTimestamp - genesisTimestamp) / 12) +const reportSlot = Math.floor((reportTimestamp - genesisTimestamp) / 12) const reportEpoch = Math.floor(reportSlot / 32) -try { - switch (requestType) { - case '1': - return await balancesHandler() - case '2': - return await detailsHandler() - default: - throw new Error('Invalid request type') - } -} catch (error) { - return Buffer.from(error.message) +switch (requestType) { + case '1': + return await balancesHandler() + case '2': + return await detailsHandler() + default: + throw new Error('Invalid request type') } async function balancesHandler() { @@ -47,7 +31,7 @@ async function balancesHandler() { const endIndex = BigInt(depositedPoolCount).toString(16).padStart(64, '0') const validatorPublicKeys = await getValidatorPublicKeys(startIndex, endIndex) - const validators = await getValidators(validatorPublicKeys) + const validators = [] // await getValidators(validatorPublicKeys) const activeBalance = validators.reduce((accumulator, { balance }) => { accumulator += parseFloat(balance) @@ -74,7 +58,7 @@ async function detailsHandler() { const endIndex = BigInt(depositedPoolCount).toString(16).padStart(64, '0') const validatorPublicKeys = await getValidatorPublicKeys(startIndex, endIndex) - const validators = await getValidators(validatorPublicKeys) + const validators = [] // await getValidators(validatorPublicKeys) const activatedDeposits = validators.reduce((accumulator, { validator }) => { const { activation_epoch } = validator @@ -225,7 +209,6 @@ async function getValidatorPublicKeys(startIndex, endIndex) { } async function getValidators(validatorPublicKeys) { - validatorPublicKeys = mockValidatorPublicKeys // TODO: remove const request = await Functions.makeHttpRequest({ url: `${ethereumBeaconUrl}/eth/v1/beacon/states/${reportSlot}/validators?id=${validatorPublicKeys.join(',')}` }) diff --git a/services/functions/Functions-request-config.js b/services/functions/Functions-request-config.js index 0681d6171..5ce7c20e7 100644 --- a/services/functions/Functions-request-config.js +++ b/services/functions/Functions-request-config.js @@ -33,25 +33,25 @@ const requestConfig = { source: fs.readFileSync(path.join(__dirname, 'API-request-source.js')).toString(), // Secrets can be accessed within the source code with `secrets.varName` (ie: secrets.apiKey). The secrets object can only contain string values. secrets: { - // ethereumRpcUrl: process.env.ETHEREUM_RPC_URL ?? "", - // ethereumBeaconRpcUrl: process.env.ETHEREUM_BEACON_RPC_URL ?? "", + ethereumRpcUrl: process.env.ETHEREUM_RPC_URL ?? "", + ethereumBeaconRpcUrl: process.env.ETHEREUM_BEACON_RPC_URL ?? "", }, // Per-node secrets objects assigned to each DON member. When using per-node secrets, nodes can only use secrets which they have been assigned. perNodeSecrets: [], // ETH wallet key used to sign secrets so they cannot be accessed by a 3rd party - walletPrivateKey: process.env["PRIVATE_KEY"], + walletPrivateKey: process.env.PRIVATE_KEY ?? "", // Args (string only array) can be accessed within the source code with `args[index]` (ie: args[0]). args: [ "1616508000", // genesisTimestamp - "1689541170", // previousReportTimestamp - "1689627688", // reportTimestamp - "9340689", // reportBlockNumber - "1", // requestType - "0x6b34d231b467fccebdc766187f7251795281dc26", // viewsAddress + "0x5D908fa3128E5Ee0EAa4be3F7894F7814B4e6489", // viewsAddress "0x0812a9fe", // getCompoundablePoolIds(uint256,uint256) "0x5d1e0780", // getDepositedPoolCount() "0x12c3456b", // getSweptBalance(uint256,uint256) - "0xf6647134" // getValidatorPublicKeys(uint256,uint256) + "0xf6647134", // getValidatorPublicKeys(uint256,uint256) + "0", // previousReportTimestamp + "0", // reportTimestamp + "0", // reportBlockNumber + "0" // requestType ], // Expected type of the returned value expectedReturnType: ReturnType.Buffer, diff --git a/services/functions/scripts/dev.ts b/services/functions/scripts/dev.ts index 47e075e69..3afd762c0 100644 --- a/services/functions/scripts/dev.ts +++ b/services/functions/scripts/dev.ts @@ -6,8 +6,10 @@ import { run } from '@casimir/shell' void async function() { process.env.BIP39_SEED = process.env.BIP39_SEED || 'inflict ball claim confirm cereal cost note dad mix donate traffic patient' process.env.BIP39_PATH_INDEX = process.env.BIP39_PATH_INDEX || '5' + process.env.ETHEREUM_RPC_URL = process.env.ETHEREUM_RPC_URL || 'http://127.0.0.1:8545' + process.env.ETHEREUM_BEACON_RPC_URL = process.env.ETHEREUM_BEACON_RPC_URL || 'http://127.0.0.1:5052' if (!process.env.FUNCTIONS_BILLING_REGISTRY_ADDRESS) throw new Error('No functions billing registry address provided') - if (!process.env.UPKEEP_ADDRESS) throw new Error('No upkeep address provided') + if (!process.env.FUNCTIONS_ORACLE_ADDRESS) throw new Error('No functions oracle address provided') run('npx esno -r dotenv/config src/index.ts') console.log('🔗 Functions service started') diff --git a/services/functions/src/providers/format.ts b/services/functions/src/providers/format.ts index 6469fa456..df8017b65 100644 --- a/services/functions/src/providers/format.ts +++ b/services/functions/src/providers/format.ts @@ -3,7 +3,7 @@ import cbor from 'cbor' /** * Decodes a CBOR hex string, and adds opening and closing brackets to the CBOR if they are not present. - * @param hexstr The hex string to decode + * @param hex The hex string to decode */ export function decodeDietCBOR(hex: string) { const buf = hexToBuf(hex) diff --git a/services/functions/src/providers/handlers.ts b/services/functions/src/providers/handlers.ts index c298e6e9d..4ba1d6c13 100644 --- a/services/functions/src/providers/handlers.ts +++ b/services/functions/src/providers/handlers.ts @@ -1,13 +1,41 @@ +import { ethers } from 'ethers' import { decodeDietCBOR } from './format' import { HandlerInput } from '../interfaces/HandlerInput' -import { simulateRequest, getDecodedResultLog, getRequestConfig } from '../../FunctionsSandboxLibrary' +import requestConfig from '@casimir/functions/Functions-request-config' +import { simulateRequest } from '../../FunctionsSandboxLibrary' +import { getConfig } from './config' + +const config = getConfig() export async function fulfillRequestHandler(input: HandlerInput): Promise { const { requestId, data } = input.args if (!requestId) throw new Error('No request id provided') if (!data) throw new Error('No data provided') - console.log(`ORACLE REQUEST ID ${requestId}`) - console.log(`ORACLE REQUEST DATA ${data}`) - console.log(`ORACLE REQUEST DECODED DATA ${JSON.stringify(decodeDietCBOR(data))}`) + const provider = new ethers.providers.JsonRpcProvider(config.ethereumUrl) + const signer = config.wallet.connect(provider) + const functionsBillingRegistry = new ethers.Contract(config.functionsBillingRegistryAddress, config.functionsBillingRegistryAbi, signer) as ethers.Contract + const { args } = decodeDietCBOR(data) + const currentRequestConfig = { + ...requestConfig, + args + } + const { result, success } = await simulateRequest(currentRequestConfig) + const response = success ? result : '0x' + const executionError = success ? '0x' : result + const dummySigners = Array(31).fill(signer.address) + // const fulfillAndBill = await functionsBillingRegistry.fulfillAndBill( + // requestId, + // response, + // executionError, + // signer.address, + // dummySigners, + // 4, + // 100_000, + // 500_000, + // { + // gasLimit: 500_000, + // } + // ) + // await fulfillAndBill.wait() } \ No newline at end of file