From ccf51fcbdde0ea04cd715007816a494e9ac31502 Mon Sep 17 00:00:00 2001 From: livingrockrises <90545960+livingrockrises@users.noreply.github.com> Date: Fri, 22 Mar 2024 16:44:26 +0400 Subject: [PATCH] deployment config and update hardhat etherscan --- .../test/helpers/MockChainlinkAggregator.sol | 113 ---- hardhat.config.ts | 32 + lib/openzeppelin-contracts | 1 + lib/scw-contracts | 2 +- package.json | 5 +- scripts/utils/index.ts | 4 +- .../oracle-aggregator-specs.ts | 135 ---- .../oracle-aggregator-specs.ts | 578 +++++++----------- yarn.lock | 28 +- 9 files changed, 286 insertions(+), 612 deletions(-) delete mode 100644 contracts/test/helpers/MockChainlinkAggregator.sol create mode 160000 lib/openzeppelin-contracts diff --git a/contracts/test/helpers/MockChainlinkAggregator.sol b/contracts/test/helpers/MockChainlinkAggregator.sol deleted file mode 100644 index 323b5b6..0000000 --- a/contracts/test/helpers/MockChainlinkAggregator.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "../../token/oracles/IOracleAggregator.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import "hardhat/console.sol"; - -/** - * @title Mock Oracle Aggregator contract - * @notice DO NOT use this in any environment for use - */ -contract MockChainlinkOracleAggregator is Ownable, IOracleAggregator { - struct TokenInfo { - /* Number of decimals represents the precision of the price returned by the feed. For example, - a price of $100.50 might be represented as 100500000000 in the contract, with 9 decimal places - of precision */ - uint8 decimals; - bool dataSigned; - address callAddress; - bytes callData; - } - - mapping(address => TokenInfo) internal tokensInfo; - - constructor(address _owner) { - _transferOwnership(_owner); - } - - /** - * @dev set price feed information for specific feed - * @param callAddress price feed / derived price feed address to call - * @param decimals decimals (precision) defined in this price feed - * @param callData function selector which will be used to query price data - * @param signed if the feed may return result as signed integrer - */ - function setTokenOracle( - address token, - address callAddress, - uint8 decimals, - bytes calldata callData, - bool signed - ) external onlyOwner { - require( - callAddress != address(0), - "ChainlinkOracleAggregator:: call address can not be zero" - ); - require( - token != address(0), - "ChainlinkOracleAggregator:: token address can not be zero" - ); - tokensInfo[token].callAddress = callAddress; - tokensInfo[token].decimals = decimals; - tokensInfo[token].callData = callData; - tokensInfo[token].dataSigned = signed; - } - - /** - * @dev query deciamls used by set feed for specific token - * @param token ERC20 token address - */ - function getTokenOracleDecimals( - address token - ) external view returns (uint8 _tokenOracleDecimals) { - _tokenOracleDecimals = tokensInfo[token].decimals; - } - - /** - * @dev query price feed - * @param token ERC20 token address - */ - function getTokenPrice( - address token - ) external view returns (uint256 tokenPrice) { - // usually token / native (depends on price feed) - tokenPrice = _getTokenPrice(token); - } - - /** - * @dev exchangeRate : each aggregator implements this method based on how it sources the quote/price - * @notice here it is token / native sourced from chainlink so in order to get defined exchangeRate we inverse the feed - * @param token ERC20 token address - */ - function getTokenValueOfOneNativeToken( - address token - ) external view virtual returns (uint256 exchangeRate) { - // we'd actually want eth / token - uint256 tokenPriceUnadjusted = _getTokenPrice(token); - uint8 _tokenOracleDecimals = tokensInfo[token].decimals; - exchangeRate = - ((10 ** _tokenOracleDecimals) * - (10 ** IERC20Metadata(token).decimals())) / - tokenPriceUnadjusted; - } - - // Making explicit revert or make use of stale price feed which reverts - // like done in below function and the test case - - function _getTokenPrice( - address token - ) internal view returns (uint256 tokenPriceUnadjusted) { - (bool success, bytes memory ret) = tokensInfo[token] - .callAddress - .staticcall(tokensInfo[token].callData); - - require(success, "ChainlinkOracleAggregator:: query failed"); - if (tokensInfo[token].dataSigned) { - tokenPriceUnadjusted = uint256(abi.decode(ret, (int256))); - } else { - tokenPriceUnadjusted = abi.decode(ret, (uint256)); - } - } -} diff --git a/hardhat.config.ts b/hardhat.config.ts index 82b0711..1fe4aad 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -90,6 +90,15 @@ const config: HardhatUserConfig = { ? [process.env.PRIVATE_KEY] : walletUtils.makeKeyList(), }, + polygon_amoy: { + url: + process.env.POLYGON_AMOY_URL || "https://rpc-amoy.polygon.technology/", + chainId: 80002, + accounts: + process.env.PRIVATE_KEY !== undefined + ? [process.env.PRIVATE_KEY] + : walletUtils.makeKeyList(), + }, bnb_mainnet: { url: "https://bsc-dataseed2.binance.org", chainId: 56, @@ -109,6 +118,11 @@ const config: HardhatUserConfig = { : walletUtils.makeKeyList(), gasPrice: 50e9, }, + blastMainnet: { + url: process.env.BLAST_MAINNET_URL || "", + accounts: hardhatAccounts, + chainId: 81457, + }, baseGoerli: { url: process.env.BASE_TESTNET_URL || @@ -220,6 +234,15 @@ const config: HardhatUserConfig = { gasPrice: 10e9, chainId: 420, }, + optimismSepolia: { + url: `https://sepolia.optimism.io/`, + accounts: + process.env.PRIVATE_KEY !== undefined + ? [process.env.PRIVATE_KEY] + : walletUtils.makeKeyList(), + gasPrice: 1e9, + chainId: 11155420, + }, optimismMainnet: { url: `https://mainnet.optimism.io`, accounts: @@ -318,6 +341,7 @@ const config: HardhatUserConfig = { arbitrumTestnet: process.env.ARBITRUM_API_KEY || "", arbitrumOne: process.env.ARBITRUM_API_KEY || "", optimisticGoerli: process.env.OPTIMISTIC_API_KEY || "", + optimismSepolia: process.env.OPTIMISTIC_API_KEY || "", optimisticEthereum: process.env.OPTIMISTIC_API_KEY || "", "base-goerli": "PLACEHOLDER_STRING", "linea-goerli": "PLACEHOLDER_STRING", @@ -445,6 +469,14 @@ const config: HardhatUserConfig = { browserURL: "https://testnet-zkevm.polygonscan.com", }, }, + { + network: "optimismSepolia", + chainId: 11155420, + urls: { + apiURL: "https://api-sepolia-optimism.etherscan.io/api", + browserURL: "https://sepolia-optimism.etherscan.io/", + }, + }, ], }, }; diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..fd81a96 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit fd81a96f01cc42ef1c9a5399364968d0e07e9e90 diff --git a/lib/scw-contracts b/lib/scw-contracts index b5be539..78694bf 160000 --- a/lib/scw-contracts +++ b/lib/scw-contracts @@ -1 +1 @@ -Subproject commit b5be5399ef0e411b62b4342e12a15e34c35bb83c +Subproject commit 78694bf4a9d843fcda8e7ab495280f85b51397f2 diff --git a/package.json b/package.json index c1a39e3..e5f350b 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@nomicfoundation/hardhat-network-helpers": "^1.0.8", "@nomicfoundation/hardhat-toolbox": "^2.0.2", "@nomiclabs/hardhat-ethers": "^2.2.3", - "@nomiclabs/hardhat-etherscan": "^3.1.7", + "@nomiclabs/hardhat-etherscan": "^3.1.8", "@nomiclabs/hardhat-waffle": "^2.0.1", "@typechain/ethers-v5": "^10.2.0", "@typechain/hardhat": "^6.1.5", @@ -94,6 +94,7 @@ "@ethersproject/abstract-signer": "^5.6.2", "@ethersproject/constants": "^5.6.1", "@mean-finance/uniswap-v3-oracle": "^1.0.3", + "@nomicfoundation/hardhat-verify": "^2.0.5", "@openzeppelin/contracts": "4.8.1", "@openzeppelin/contracts-upgradeable": "4.8.1", "@pimlico/erc20-paymaster": "^0.0.1", @@ -112,4 +113,4 @@ "solidity-bytes-utils": "^0.8.0", "source-map-support": "^0.5.19" } -} \ No newline at end of file +} diff --git a/scripts/utils/index.ts b/scripts/utils/index.ts index 629be36..06ef915 100644 --- a/scripts/utils/index.ts +++ b/scripts/utils/index.ts @@ -271,8 +271,8 @@ export const deployContract = async ( // TODO // Review gas price const { hash, wait } = await deployerInstance.deploy(salt, contractByteCode, { - maxFeePerGas: 3e9, - maxPriorityFeePerGas: 2e9, + maxFeePerGas: 1e9, + maxPriorityFeePerGas: 1e6, }); console.log(`Submitted transaction ${hash} for deployment`); diff --git a/test/bundler-integration/token-paymaster/oracle-aggregator-specs.ts b/test/bundler-integration/token-paymaster/oracle-aggregator-specs.ts index 5f87455..57e1969 100644 --- a/test/bundler-integration/token-paymaster/oracle-aggregator-specs.ts +++ b/test/bundler-integration/token-paymaster/oracle-aggregator-specs.ts @@ -14,13 +14,9 @@ import { BiconomyTokenPaymaster__factory, ChainlinkOracleAggregator, ChainlinkOracleAggregator__factory, - MockChainlinkOracleAggregator__factory, - MockPriceFeed, MockStalePriceFeed__factory, - MockStalePriceFeed, MockPriceFeed__factory, MockToken, - MockChainlinkOracleAggregator, } from "../../../typechain-types"; import { EcdsaOwnershipRegistryModule, @@ -97,7 +93,6 @@ describe("Biconomy Token Paymaster (With Bundler)", function () { let sampleTokenPaymaster: BiconomyTokenPaymaster; let oracleAggregator: ChainlinkOracleAggregator; - let staleOracleAggregator: MockChainlinkOracleAggregator; // Could also use published package or added submodule (for Account Implementation and Factory) let smartWalletImp: BiconomyAccountImplementation; @@ -130,9 +125,6 @@ describe("Biconomy Token Paymaster (With Bundler)", function () { oracleAggregator = await new ChainlinkOracleAggregator__factory( deployer ).deploy(walletOwnerAddress); - staleOracleAggregator = await new MockChainlinkOracleAggregator__factory( - deployer - ).deploy(walletOwnerAddress); ecdsaModule = await new EcdsaOwnershipRegistryModule__factory( deployer @@ -174,14 +166,6 @@ describe("Biconomy Token Paymaster (With Bundler)", function () { const priceFeedTxStale: any = await priceFeedStale.populateTransaction.getThePrice(); - await staleOracleAggregator.setTokenOracle( - token.address, - stalePriceFeedMock.address, - 18, - priceFeedTxStale.data, - true - ); - const priceResult = await oracleAggregator.getTokenValueOfOneNativeToken( token.address ); @@ -250,125 +234,6 @@ describe("Biconomy Token Paymaster (With Bundler)", function () { }); describe("Token Paymaster with good and bad oracle aggregator", () => { - it("succeed with fallback exchange rate in case price feed reverts", async () => { - const userSCW: any = BiconomyAccountImplementation__factory.connect( - walletAddress, - deployer - ); - - await token - .connect(deployer) - .transfer(walletAddress, ethers.utils.parseEther("100")); - - const owner = await walletOwner.getAddress(); - const AccountFactory = await ethers.getContractFactory( - "SmartAccountFactory" - ); - const ecdsaOwnershipSetupData = ecdsaModule.interface.encodeFunctionData( - "initForSmartAccount", - [owner] - ); - - const smartAccountDeploymentIndex = 0; - - const deploymentData = AccountFactory.interface.encodeFunctionData( - "deployCounterFactualAccount", - [ - ecdsaModule.address, - ecdsaOwnershipSetupData, - smartAccountDeploymentIndex, - ] - ); - - const userOp1 = await fillAndSign( - { - sender: walletAddress, - verificationGasLimit: 200000, - preVerificationGas: 50000, - callData: encodeERC20Approval( - userSCW, - token, - paymasterAddress, - ethers.constants.MaxUint256 - ), - }, - walletOwner, - entryPoint, - "nonce" - ); - - const hash = await sampleTokenPaymaster.getHash( - userOp1, - ethers.utils.hexlify(1).slice(2, 4), - MOCK_VALID_UNTIL, - MOCK_VALID_AFTER, - token.address, - staleOracleAggregator.address, - MOCK_FX, - DEFAULT_FEE_MARKUP - ); - const sig = await offchainSigner.signMessage(arrayify(hash)); - const userOp = await fillAndSign( - { - ...userOp1, - paymasterAndData: ethers.utils.hexConcat([ - paymasterAddress, - ethers.utils.hexlify(1).slice(0, 4), - encodePaymasterData( - token.address, - staleOracleAggregator.address, - MOCK_FX, - DEFAULT_FEE_MARKUP - ), - sig, - ]), - }, - walletOwner, - entryPoint, - "nonce" - ); - - const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode( - ["bytes", "address"], - [userOp.signature, ecdsaModule.address] - ); - - userOp.signature = signatureWithModuleAddress; - - const { result: userOpHash } = await environment.sendUserOperation( - userOp, - entryPoint.address - ); - - const { - result: { - receipt: { transactionHash }, - }, - } = await environment.getUserOperationReceipt(userOpHash); - const receipt = await ethers.provider.getTransactionReceipt( - transactionHash - ); - - const ev = await getUserOpEvent(entryPoint); - expect(ev.args.success).to.be.true; - - const BiconomyTokenPaymaster = await ethers.getContractFactory( - "BiconomyTokenPaymaster" - ); - - const eventLogs = BiconomyTokenPaymaster.interface.decodeEventLog( - "TokenPaymasterOperation", - receipt.logs[3].data - ); - - // Confirming that it's using backup (external) exchange rate in case oracle aggregator / price feed is stale / anything goes wrong - expect(eventLogs.exchangeRate.toString()).to.be.equal(MOCK_FX); - - await expect( - entryPoint.handleOps([userOp], await offchainSigner.getAddress()) - ).to.be.reverted; - }); - it("succeed with exchange rate based on prcie feed in case everything goes well", async () => { const userSCW: any = BiconomyAccountImplementation__factory.connect( walletAddress, diff --git a/test/token-paymaster/oracle-aggregator-specs.ts b/test/token-paymaster/oracle-aggregator-specs.ts index 1ec4923..9cb52e2 100644 --- a/test/token-paymaster/oracle-aggregator-specs.ts +++ b/test/token-paymaster/oracle-aggregator-specs.ts @@ -14,13 +14,9 @@ import { BiconomyTokenPaymaster__factory, ChainlinkOracleAggregator, ChainlinkOracleAggregator__factory, - MockChainlinkOracleAggregator__factory, - MockPriceFeed, MockStalePriceFeed__factory, - MockStalePriceFeed, MockPriceFeed__factory, MockToken, - MockChainlinkOracleAggregator, } from "../../typechain-types"; // Review: Could import from scw-contracts submodules to be consistent @@ -104,10 +100,7 @@ describe("Biconomy Token Paymaster", function () { let offchainSigner: Signer, deployer: Signer; let sampleTokenPaymaster: BiconomyTokenPaymaster; - let mockPriceFeed: MockPriceFeed; let oracleAggregator: ChainlinkOracleAggregator; - let staleOracleAggregator: MockChainlinkOracleAggregator; - let staleMockPriceFeed: MockStalePriceFeed; let ecdsaModule: EcdsaOwnershipRegistryModule; let smartWalletImp: BiconomyAccountImplementation; @@ -133,13 +126,10 @@ describe("Biconomy Token Paymaster", function () { ecdsaModule = await new EcdsaOwnershipRegistryModule__factory( deployer ).deploy(); - staleOracleAggregator = await new MockChainlinkOracleAggregator__factory( - deployer - ).deploy(walletOwnerAddress); const MockToken = await ethers.getContractFactory("MockToken"); token = await MockToken.deploy(); - + await token.deployed(); const usdcMaticPriceFeedMock = await new MockPriceFeed__factory( @@ -174,14 +164,6 @@ describe("Biconomy Token Paymaster", function () { const priceFeedTxStale: any = await priceFeedStale.populateTransaction.getThePrice(); - await staleOracleAggregator.setTokenOracle( - token.address, - stalePriceFeedMock.address, - 18, - priceFeedTxStale.data, - true - ); - sampleTokenPaymaster = await new BiconomyTokenPaymaster__factory( deployer ).deploy( @@ -240,346 +222,232 @@ describe("Biconomy Token Paymaster", function () { const rate1 = await oracleAggregator.getTokenValueOfOneNativeToken( token.address ); - - await expect( - staleOracleAggregator.getTokenValueOfOneNativeToken(token.address) - ).to.be.reverted; }); - }); - - describe("Token Payamster with good and bad oracle aggregator", () => { - it("succeed with fallback exchange rate in case price feed reverts", async () => { - const userSCW: any = BiconomyAccountImplementation__factory.connect( - walletAddress, - deployer - ); - - await token - .connect(deployer) - .transfer(walletAddress, ethers.utils.parseEther("100")); - - const owner = await walletOwner.getAddress(); - const AccountFactory = await ethers.getContractFactory( - "SmartAccountFactory" - ); - const ecdsaOwnershipSetupData = ecdsaModule.interface.encodeFunctionData( - "initForSmartAccount", - [owner] - ); - - const smartAccountDeploymentIndex = 0; - - const deploymentData = AccountFactory.interface.encodeFunctionData( - "deployCounterFactualAccount", - [ - ecdsaModule.address, - ecdsaOwnershipSetupData, - smartAccountDeploymentIndex, - ] - ); - - const userOp1 = await fillAndSign( - { - sender: walletAddress, - verificationGasLimit: 200000, - callData: encodeERC20Approval( - userSCW, - token, - paymasterAddress, - ethers.constants.MaxUint256 - ), - }, - walletOwner, - entryPoint, - "nonce" - ); - - const hash = await sampleTokenPaymaster.getHash( - userOp1, - ethers.utils.hexlify(1).slice(2, 4), - MOCK_VALID_UNTIL, - MOCK_VALID_AFTER, - token.address, - staleOracleAggregator.address, - MOCK_FX, - DEFAULT_FEE_MARKUP - ); - const sig = await offchainSigner.signMessage(arrayify(hash)); - const userOp = await fillAndSign( - { - ...userOp1, - paymasterAndData: ethers.utils.hexConcat([ - paymasterAddress, - ethers.utils.hexlify(1).slice(0, 4), - encodePaymasterData( - token.address, - staleOracleAggregator.address, - MOCK_FX, - DEFAULT_FEE_MARKUP - ), - sig, - ]), - }, - walletOwner, - entryPoint, - "nonce" - ); - - const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode( - ["bytes", "address"], - [userOp.signature, ecdsaModule.address] - ); - - userOp.signature = signatureWithModuleAddress; - - const tx = await entryPoint.handleOps( - [userOp], - await offchainSigner.getAddress() - ); - const receipt = await tx.wait(); - - const ev = await getUserOpEvent(entryPoint); - expect(ev.args.success).to.be.true; - - const BiconomyTokenPaymaster = await ethers.getContractFactory( - "BiconomyTokenPaymaster" - ); - - const eventLogs = BiconomyTokenPaymaster.interface.decodeEventLog( - "TokenPaymasterOperation", - receipt.logs[3].data - ); - - // Confirming that it's using backup (external) exchange rate in case oracle aggregator / price feed is stale / anything goes wrong - expect(eventLogs.exchangeRate.toString()).to.be.equal(MOCK_FX); - - await expect( - entryPoint.handleOps([userOp], await offchainSigner.getAddress()) - ).to.be.reverted; - }); - - it("succeed with exchange rate based on price feed in case everything goes well", async () => { - const userSCW: any = BiconomyAccountImplementation__factory.connect( - walletAddress, - deployer - ); - - const rate1 = await oracleAggregator.getTokenValueOfOneNativeToken( - token.address - ); - - await token - .connect(deployer) - .transfer(walletAddress, ethers.utils.parseEther("100")); - - const owner = await walletOwner.getAddress(); - const AccountFactory = await ethers.getContractFactory( - "SmartAccountFactory" - ); - const ecdsaOwnershipSetupData = ecdsaModule.interface.encodeFunctionData( - "initForSmartAccount", - [owner] - ); - - const smartAccountDeploymentIndex = 0; - - const deploymentData = AccountFactory.interface.encodeFunctionData( - "deployCounterFactualAccount", - [ - ecdsaModule.address, - ecdsaOwnershipSetupData, - smartAccountDeploymentIndex, - ] - ); - - const userOp1 = await fillAndSign( - { - sender: walletAddress, - verificationGasLimit: 200000, - callData: encodeERC20Approval( - userSCW, - token, - paymasterAddress, - ethers.constants.MaxUint256 - ), - }, - walletOwner, - entryPoint, - "nonce" - ); - const hash = await sampleTokenPaymaster.getHash( - userOp1, - ethers.utils.hexlify(1).slice(2, 4), - MOCK_VALID_UNTIL, - MOCK_VALID_AFTER, - token.address, - oracleAggregator.address, - MOCK_FX, - DEFAULT_FEE_MARKUP - ); - const sig = await offchainSigner.signMessage(arrayify(hash)); - const userOp = await fillAndSign( - { - ...userOp1, - paymasterAndData: ethers.utils.hexConcat([ - paymasterAddress, - ethers.utils.hexlify(1).slice(0, 4), - encodePaymasterData( - token.address, - oracleAggregator.address, - MOCK_FX, - DEFAULT_FEE_MARKUP + describe("Token Payamster with good and bad oracle aggregator", () => { + it("succeed with exchange rate based on price feed in case everything goes well", async () => { + const userSCW: any = BiconomyAccountImplementation__factory.connect( + walletAddress, + deployer + ); + + const rate1 = await oracleAggregator.getTokenValueOfOneNativeToken( + token.address + ); + + await token + .connect(deployer) + .transfer(walletAddress, ethers.utils.parseEther("100")); + + const owner = await walletOwner.getAddress(); + const AccountFactory = await ethers.getContractFactory( + "SmartAccountFactory" + ); + const ecdsaOwnershipSetupData = + ecdsaModule.interface.encodeFunctionData("initForSmartAccount", [ + owner, + ]); + + const smartAccountDeploymentIndex = 0; + + const deploymentData = AccountFactory.interface.encodeFunctionData( + "deployCounterFactualAccount", + [ + ecdsaModule.address, + ecdsaOwnershipSetupData, + smartAccountDeploymentIndex, + ] + ); + + const userOp1 = await fillAndSign( + { + sender: walletAddress, + verificationGasLimit: 200000, + callData: encodeERC20Approval( + userSCW, + token, + paymasterAddress, + ethers.constants.MaxUint256 ), - sig, - ]), - }, - walletOwner, - entryPoint, - "nonce" - ); - - const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode( - ["bytes", "address"], - [userOp.signature, ecdsaModule.address] - ); - - userOp.signature = signatureWithModuleAddress; - - const tx = await entryPoint.handleOps( - [userOp], - await offchainSigner.getAddress() - ); - const receipt = await tx.wait(); - - const ev = await getUserOpEvent(entryPoint); - expect(ev.args.success).to.be.true; - - const BiconomyTokenPaymaster = await ethers.getContractFactory( - "BiconomyTokenPaymaster" - ); - - const eventLogs = BiconomyTokenPaymaster.interface.decodeEventLog( - "TokenPaymasterOperation", - receipt.logs[3].data - ); - - // Confirming that it's using backup (external) exchange rate in case oracle aggregator / price feed is stale / anything goes wrong - expect(eventLogs.exchangeRate).to.be.equal(rate1); - - await expect( - entryPoint.handleOps([userOp], await offchainSigner.getAddress()) - ).to.be.reverted; - }); - - it("succeed with fallback exchange rate in case oracle aggregator is 0 address", async () => { - const userSCW: any = BiconomyAccountImplementation__factory.connect( - walletAddress, - deployer - ); - - await token - .connect(deployer) - .transfer(walletAddress, ethers.utils.parseEther("100")); - - const owner = await walletOwner.getAddress(); - const AccountFactory = await ethers.getContractFactory( - "SmartAccountFactory" - ); - const ecdsaOwnershipSetupData = ecdsaModule.interface.encodeFunctionData( - "initForSmartAccount", - [owner] - ); - - const smartAccountDeploymentIndex = 0; - - const deploymentData = AccountFactory.interface.encodeFunctionData( - "deployCounterFactualAccount", - [ - ecdsaModule.address, - ecdsaOwnershipSetupData, - smartAccountDeploymentIndex, - ] - ); - - const userOp1 = await fillAndSign( - { - sender: walletAddress, - verificationGasLimit: 200000, - callData: encodeERC20Approval( - userSCW, - token, - paymasterAddress, - ethers.constants.MaxUint256 - ), - }, - walletOwner, - entryPoint, - "nonce" - ); - - const hash = await sampleTokenPaymaster.getHash( - userOp1, - ethers.utils.hexlify(1).slice(2, 4), - MOCK_VALID_UNTIL, - MOCK_VALID_AFTER, - token.address, - ethers.constants.AddressZero, - MOCK_FX, - DEFAULT_FEE_MARKUP - ); - const sig = await offchainSigner.signMessage(arrayify(hash)); - const userOp = await fillAndSign( - { - ...userOp1, - paymasterAndData: ethers.utils.hexConcat([ - paymasterAddress, - ethers.utils.hexlify(1).slice(0, 4), - encodePaymasterData( - token.address, - ethers.constants.AddressZero, - MOCK_FX, - DEFAULT_FEE_MARKUP + }, + walletOwner, + entryPoint, + "nonce" + ); + + const hash = await sampleTokenPaymaster.getHash( + userOp1, + ethers.utils.hexlify(1).slice(2, 4), + MOCK_VALID_UNTIL, + MOCK_VALID_AFTER, + token.address, + oracleAggregator.address, + MOCK_FX, + DEFAULT_FEE_MARKUP + ); + const sig = await offchainSigner.signMessage(arrayify(hash)); + const userOp = await fillAndSign( + { + ...userOp1, + paymasterAndData: ethers.utils.hexConcat([ + paymasterAddress, + ethers.utils.hexlify(1).slice(0, 4), + encodePaymasterData( + token.address, + oracleAggregator.address, + MOCK_FX, + DEFAULT_FEE_MARKUP + ), + sig, + ]), + }, + walletOwner, + entryPoint, + "nonce" + ); + + const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode( + ["bytes", "address"], + [userOp.signature, ecdsaModule.address] + ); + + userOp.signature = signatureWithModuleAddress; + + const tx = await entryPoint.handleOps( + [userOp], + await offchainSigner.getAddress() + ); + const receipt = await tx.wait(); + + const ev = await getUserOpEvent(entryPoint); + expect(ev.args.success).to.be.true; + + const BiconomyTokenPaymaster = await ethers.getContractFactory( + "BiconomyTokenPaymaster" + ); + + const eventLogs = BiconomyTokenPaymaster.interface.decodeEventLog( + "TokenPaymasterOperation", + receipt.logs[3].data + ); + + // Confirming that it's using backup (external) exchange rate in case oracle aggregator / price feed is stale / anything goes wrong + expect(eventLogs.exchangeRate).to.be.equal(rate1); + + await expect( + entryPoint.handleOps([userOp], await offchainSigner.getAddress()) + ).to.be.reverted; + }); + + it("succeed with fallback exchange rate in case oracle aggregator is 0 address", async () => { + const userSCW: any = BiconomyAccountImplementation__factory.connect( + walletAddress, + deployer + ); + + await token + .connect(deployer) + .transfer(walletAddress, ethers.utils.parseEther("100")); + + const owner = await walletOwner.getAddress(); + const AccountFactory = await ethers.getContractFactory( + "SmartAccountFactory" + ); + const ecdsaOwnershipSetupData = + ecdsaModule.interface.encodeFunctionData("initForSmartAccount", [ + owner, + ]); + + const smartAccountDeploymentIndex = 0; + + const deploymentData = AccountFactory.interface.encodeFunctionData( + "deployCounterFactualAccount", + [ + ecdsaModule.address, + ecdsaOwnershipSetupData, + smartAccountDeploymentIndex, + ] + ); + + const userOp1 = await fillAndSign( + { + sender: walletAddress, + verificationGasLimit: 200000, + callData: encodeERC20Approval( + userSCW, + token, + paymasterAddress, + ethers.constants.MaxUint256 ), - sig, - ]), - }, - walletOwner, - entryPoint, - "nonce" - ); - - const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode( - ["bytes", "address"], - [userOp.signature, ecdsaModule.address] - ); - - userOp.signature = signatureWithModuleAddress; - - const tx = await entryPoint.handleOps( - [userOp], - await offchainSigner.getAddress() - ); - const receipt = await tx.wait(); - - const ev = await getUserOpEvent(entryPoint); - expect(ev.args.success).to.be.true; - - const BiconomyTokenPaymaster = await ethers.getContractFactory( - "BiconomyTokenPaymaster" - ); - - const eventLogs = BiconomyTokenPaymaster.interface.decodeEventLog( - "TokenPaymasterOperation", - receipt.logs[3].data - ); - - // Confirming that it's using backup (external) exchange rate in case oracle aggregator / price feed is stale / anything goes wrong - expect(eventLogs.exchangeRate).to.be.equal(MOCK_FX); - - await expect( - entryPoint.handleOps([userOp], await offchainSigner.getAddress()) - ).to.be.reverted; + }, + walletOwner, + entryPoint, + "nonce" + ); + + const hash = await sampleTokenPaymaster.getHash( + userOp1, + ethers.utils.hexlify(1).slice(2, 4), + MOCK_VALID_UNTIL, + MOCK_VALID_AFTER, + token.address, + ethers.constants.AddressZero, + MOCK_FX, + DEFAULT_FEE_MARKUP + ); + const sig = await offchainSigner.signMessage(arrayify(hash)); + const userOp = await fillAndSign( + { + ...userOp1, + paymasterAndData: ethers.utils.hexConcat([ + paymasterAddress, + ethers.utils.hexlify(1).slice(0, 4), + encodePaymasterData( + token.address, + ethers.constants.AddressZero, + MOCK_FX, + DEFAULT_FEE_MARKUP + ), + sig, + ]), + }, + walletOwner, + entryPoint, + "nonce" + ); + + const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode( + ["bytes", "address"], + [userOp.signature, ecdsaModule.address] + ); + + userOp.signature = signatureWithModuleAddress; + + const tx = await entryPoint.handleOps( + [userOp], + await offchainSigner.getAddress() + ); + const receipt = await tx.wait(); + + const ev = await getUserOpEvent(entryPoint); + expect(ev.args.success).to.be.true; + + const BiconomyTokenPaymaster = await ethers.getContractFactory( + "BiconomyTokenPaymaster" + ); + + const eventLogs = BiconomyTokenPaymaster.interface.decodeEventLog( + "TokenPaymasterOperation", + receipt.logs[3].data + ); + + // Confirming that it's using backup (external) exchange rate in case oracle aggregator / price feed is stale / anything goes wrong + expect(eventLogs.exchangeRate).to.be.equal(MOCK_FX); + + await expect( + entryPoint.handleOps([userOp], await offchainSigner.getAddress()) + ).to.be.reverted; + }); }); }); }); diff --git a/yarn.lock b/yarn.lock index cfe90ff..7eab89d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1174,6 +1174,21 @@ resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-2.0.2.tgz#ec95f23b53cb4e71a1a7091380fa223aad18f156" integrity sha512-vnN1AzxbvpSx9pfdRHbUzTRIXpMLPXnUlkW855VaDk6N1pwRaQ2gNzEmFAABk4lWf11E00PKwFd/q27HuwYrYg== +"@nomicfoundation/hardhat-verify@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.0.5.tgz#dcc2cb5e5c55a39704c7d492436f80f05a4ca5a3" + integrity sha512-Tg4zu8RkWpyADSFIgF4FlJIUEI4VkxcvELsmbJn2OokbvH2SnUrqKmw0BBfDrtvP0hhmx8wsnrRKP5DV/oTyTA== + dependencies: + "@ethersproject/abi" "^5.1.2" + "@ethersproject/address" "^5.0.2" + cbor "^8.1.0" + chalk "^2.4.2" + debug "^4.1.1" + lodash.clonedeep "^4.5.0" + semver "^6.3.0" + table "^6.8.0" + undici "^5.14.0" + "@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz#4c858096b1c17fe58a474fe81b46815f93645c15" @@ -1245,10 +1260,10 @@ resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz#b41053e360c31a32c2640c9a45ee981a7e603fe0" integrity sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg== -"@nomiclabs/hardhat-etherscan@^3.1.7": - version "3.1.7" - resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.7.tgz#72e3d5bd5d0ceb695e097a7f6f5ff6fcbf062b9a" - integrity sha512-tZ3TvSgpvsQ6B6OGmo1/Au6u8BrAkvs1mIC/eURA3xgIfznUZBhmpne8hv7BXUzw9xNL3fXdpOYgOQlVMTcoHQ== +"@nomiclabs/hardhat-etherscan@^3.1.8": + version "3.1.8" + resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.8.tgz#3c12ee90b3733e0775e05111146ef9418d4f5a38" + integrity sha512-v5F6IzQhrsjHh6kQz4uNrym49brK9K5bYCq2zQZ729RYRaifI9hHbtmK+KkIVevfhut7huQFEQ77JLRMAzWYjQ== dependencies: "@ethersproject/abi" "^5.1.2" "@ethersproject/address" "^5.0.2" @@ -6153,6 +6168,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"