diff --git a/.github/workflows/forge-test.yml b/.github/workflows/forge-test.yml index 4aa934558..878f0f59f 100644 --- a/.github/workflows/forge-test.yml +++ b/.github/workflows/forge-test.yml @@ -50,8 +50,29 @@ jobs: - uses: ./.github/actions/install - name: Run tests - run: forge test - + run: forge test --gas-report > gasreport.ansi + env: + # make fuzzing semi-deterministic to avoid noisy gas cost estimation + # due to non-deterministic fuzzing (but still use pseudo-random fuzzing seeds) + FOUNDRY_FUZZ_SEED: 0x${{ github.event.pull_request.base.sha || github.sha }} + + - name: Compare gas reports + uses: Rubilmax/foundry-gas-diff@v3.16 + with: + summaryQuantile: 0.9 # only display the 10% most significant gas diffs in the summary (defaults to 20%) + sortCriteria: avg,max # sort diff rows by criteria + sortOrders: desc,asc # and directions + ignore: test-foundry/**/* # filter out gas reports from specific paths (test/ is included by default) + id: gas_diff + + - name: Add gas diff to sticky comment + if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' + uses: marocchino/sticky-pull-request-comment@v2 + with: + # delete the comment in case changes no longer impact gas costs + delete: ${{ !steps.gas_diff.outputs.markdown }} + message: ${{ steps.gas_diff.outputs.markdown }} + coverage: needs: - test diff --git a/.github/workflows/gas-diff.yml b/.github/workflows/gas-diff.yml deleted file mode 100644 index c50257ce9..000000000 --- a/.github/workflows/gas-diff.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Report gas diff - -on: - workflow_call: -# on: -# push: -# branches: -# - main -# pull_request: -# # Optionally configure to run only for changes in specific files. For example: -# # paths: -# # - src/** -# # - test/** -# # - foundry.toml -# # - remappings.txt -# # - .github/workflows/foundry-gas-diff.yml - -jobs: - compare_gas_reports: - name: Compare - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - uses: ./.github/actions/install - - - name: Build contracts - run: forge build --sizes - - - name: Check gas snapshots - run: forge snapshot --diff - - - name: Run tests - run: forge test --gas-report > gasreport.ansi - env: - # make fuzzing semi-deterministic to avoid noisy gas cost estimation - # due to non-deterministic fuzzing (but still use pseudo-random fuzzing seeds) - FOUNDRY_FUZZ_SEED: 0x${{ github.event.pull_request.base.sha || github.sha }} - - - name: Compare gas reports - uses: Rubilmax/foundry-gas-diff@v3.16 - with: - summaryQuantile: 0.9 # only display the 10% most significant gas diffs in the summary (defaults to 20%) - sortCriteria: avg,max # sort diff rows by criteria - sortOrders: desc,asc # and directions - ignore: test-foundry/**/* # filter out gas reports from specific paths (test/ is included by default) - id: gas_diff - - - name: Add gas diff to sticky comment - if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' - uses: marocchino/sticky-pull-request-comment@v2 - with: - # delete the comment in case changes no longer impact gas costs - delete: ${{ !steps.gas_diff.outputs.markdown }} - message: ${{ steps.gas_diff.outputs.markdown }} \ No newline at end of file diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 887092f52..b388cdd41 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -23,9 +23,4 @@ jobs: name: Slither analysis uses: ./.github/workflows/slither.yml secrets: inherit - - gas-diff: - name: Gas diff - uses: ./.github/workflows/gas-diff.yml - secrets: inherit \ No newline at end of file diff --git a/script/deploy.sol b/script/deploy.sol index 9be722f5f..28181f94f 100644 --- a/script/deploy.sol +++ b/script/deploy.sol @@ -24,7 +24,7 @@ contract KintoInitialDeployScript is Create2Helper, ArtifactsReader { SponsorPaymaster _sponsorPaymasterImpl; SponsorPaymaster _sponsorPaymaster; KintoID _implementation; - KintoID _kintoIDv1; + KintoID _kintoID; UUPSProxy _proxy; IKintoWallet _walletImpl; IKintoWallet _kintoWalletv1; @@ -62,17 +62,17 @@ contract KintoInitialDeployScript is Create2Helper, ArtifactsReader { if (isContract(kintoProxyAddr)) { _proxy = UUPSProxy(payable(kintoProxyAddr)); console.log("Already deployed Kinto ID proxy at", address(kintoProxyAddr)); - _kintoIDv1 = KintoID(address(_proxy)); + _kintoID = KintoID(address(_proxy)); } else { // deploy proxy contract and point it to implementation _proxy = new UUPSProxy{salt: 0}(address(_implementation), ""); // wrap in ABI to support easier calls - _kintoIDv1 = KintoID(address(_proxy)); - console.log("Kinto ID proxy deployed at ", address(_kintoIDv1)); + _kintoID = KintoID(address(_proxy)); + console.log("Kinto ID proxy deployed at ", address(_kintoID)); // Initialize proxy - _kintoIDv1.initialize(); + _kintoID.initialize(); } - _kintoIDv1 = KintoID(address(_proxy)); + _kintoID = KintoID(address(_proxy)); // Entry Point address entryPointAddr = computeAddress(1, abi.encodePacked(type(EntryPoint).creationCode)); @@ -88,14 +88,14 @@ contract KintoInitialDeployScript is Create2Helper, ArtifactsReader { // Wallet Implementation for the beacon address walletImplementationAddr = computeAddress( - 0, abi.encodePacked(type(KintoWallet).creationCode, abi.encode(address(_entryPoint), address(_kintoIDv1))) + 0, abi.encodePacked(type(KintoWallet).creationCode, abi.encode(address(_entryPoint), address(_kintoID))) ); if (isContract(walletImplementationAddr)) { _walletImpl = KintoWallet(payable(walletImplementationAddr)); console.log("Wallet Implementation already deployed at", address(walletImplementationAddr)); } else { // Deploy Wallet Implementation - _walletImpl = new KintoWallet{salt: 0}(_entryPoint, _kintoIDv1, IKintoAppRegistry(address(0))); + _walletImpl = new KintoWallet{salt: 0}(_entryPoint, _kintoID, IKintoAppRegistry(address(0))); console.log("Wallet Implementation deployed at", address(_walletImpl)); } @@ -129,7 +129,7 @@ contract KintoInitialDeployScript is Create2Helper, ArtifactsReader { _walletFactory = KintoWalletFactory(address(_proxy)); console.log("Wallet Factory proxy deployed at ", address(_walletFactory)); // Initialize proxy - _walletFactory.initialize(_kintoIDv1); + _walletFactory.initialize(_kintoID); } address walletFactory = _entryPoint.walletFactory(); @@ -207,7 +207,7 @@ contract KintoInitialDeployScript is Create2Helper, ArtifactsReader { // Writes the addresses to a file vm.writeFile(_getAddressesFile(), "{\n"); - vm.writeLine(_getAddressesFile(), string.concat('"KintoID": "', vm.toString(address(_kintoIDv1)), '",')); + vm.writeLine(_getAddressesFile(), string.concat('"KintoID": "', vm.toString(address(_kintoID)), '",')); vm.writeLine( _getAddressesFile(), string.concat('"KintoID-impl": "', vm.toString(address(_implementation)), '",') ); diff --git a/script/migrations/03-mint_nft_admin.sol b/script/migrations/03-mint_nft_admin.sol index 8c984c79b..832cd7437 100644 --- a/script/migrations/03-mint_nft_admin.sol +++ b/script/migrations/03-mint_nft_admin.sol @@ -13,32 +13,33 @@ import "forge-std/console.sol"; contract KintoMigration3DeployScript is Create2Helper, KYCSignature, ArtifactsReader { using ECDSAUpgradeable for bytes32; - KintoID _kintoIDv1; + KintoID _kintoID; function setUp() public {} function run() public { console.log("RUNNING ON CHAIN WITH ID", vm.toString(block.chainid)); - // If not using ledger, replace + + // if not using ledger, replace uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address deployer = vm.rememberKey(deployerPrivateKey); - vm.startBroadcast(deployerPrivateKey); + console.log("Executing with address", deployer); - // vm.startBroadcast(); address walletFactoryAddr = _getChainDeployment("KintoWalletFactory"); if (walletFactoryAddr == address(0)) { console.log("Need to execute main deploy script first", walletFactoryAddr); return; } - // Mint an nft to the owner - _kintoIDv1 = KintoID(_getChainDeployment("KintoID")); + + // mint an nft to the owner + _kintoID = KintoID(_getChainDeployment("KintoID")); IKintoID.SignatureData memory sigdata = - _auxCreateSignature(_kintoIDv1, deployer, deployer, deployerPrivateKey, block.timestamp + 1000); - vm.stopBroadcast(); - vm.startBroadcast(); + _auxCreateSignature(_kintoID, deployer, deployerPrivateKey, block.timestamp + 1000); + uint16[] memory traits = new uint16[](1); traits[0] = 0; // ADMIN - _kintoIDv1.mintIndividualKyc(sigdata, traits); - vm.stopBroadcast(); + + vm.broadcast(); + _kintoID.mintIndividualKyc(sigdata, traits); } } diff --git a/script/migrations/04-deploy_admin_wallet.sol b/script/migrations/04-deploy_admin_wallet.sol index a8046725b..6d691b7b4 100644 --- a/script/migrations/04-deploy_admin_wallet.sol +++ b/script/migrations/04-deploy_admin_wallet.sol @@ -17,7 +17,7 @@ contract KintoMigration4DeployScript is Create2Helper, ArtifactsReader { KintoWalletFactory _walletFactory; KintoWallet _kintoWalletv1; - KintoID _kintoIDv1; + KintoID _kintoID; UUPSProxy _proxy; function setUp() public {} diff --git a/script/migrations/12-create_engen_app.sol b/script/migrations/12-create_engen_app.sol index 3ec5137a4..386539e0d 100644 --- a/script/migrations/12-create_engen_app.sol +++ b/script/migrations/12-create_engen_app.sol @@ -18,7 +18,7 @@ import "../../test/helpers/UserOp.sol"; contract KintoMigration12DeployScript is ArtifactsReader, UserOp { KintoWalletFactory _walletFactory; KintoWallet _kintoWalletv1; - KintoID _kintoIDv1; + KintoID _kintoID; UUPSProxy _proxy; function setUp() public {} diff --git a/script/migrations/22-wallet_v5.sol b/script/migrations/22-wallet_v5.sol new file mode 100644 index 000000000..bdee527ee --- /dev/null +++ b/script/migrations/22-wallet_v5.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +import "../../src/wallet/KintoWallet.sol"; +import "./utils/MigrationHelper.sol"; + +// NOTE: this is a sample migration script with the new refactors +// contract KintoMigration22DeployScript is MigrationHelper { +// using ECDSAUpgradeable for bytes32; + +// function run() public { +// run2(); + +// // generate bytecode for KintoWalletV5 +// bytes memory bytecode = abi.encodePacked( +// type(KintoWalletV5).creationCode, +// abi.encode( +// _getChainDeployment("EntryPoint"), +// IKintoID(_getChainDeployment("KintoID")), +// IKintoAppRegistry(_getChainDeployment("KintoAppRegistry")) +// ) +// ); + +// // upgrade KintoWallet to V5 +// deployAndUpgrade("KintoWallet", "V5", bytecode); +// } +// } diff --git a/script/migrations/utils/MigrationHelper.sol b/script/migrations/utils/MigrationHelper.sol new file mode 100644 index 000000000..29da8d9f4 --- /dev/null +++ b/script/migrations/utils/MigrationHelper.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +import "../../../src/wallet/KintoWalletFactory.sol"; + +import "../../../src/interfaces/ISponsorPaymaster.sol"; +import "../../../src/interfaces/IKintoWallet.sol"; + +import "../../../test/helpers/Create2Helper.sol"; +import "../../../test/helpers/ArtifactsReader.sol"; +import "../../../test/helpers/UserOp.sol"; + +import "forge-std/Script.sol"; +import "forge-std/console.sol"; + +interface IInitialize { + function initialize(address anOwner, address _recoverer) external; +} + +contract MigrationHelper is Create2Helper, ArtifactsReader, UserOp { + using ECDSAUpgradeable for bytes32; + + uint256 deployerPrivateKey; + KintoWalletFactory _walletFactory; + + function run2() public { + console.log("Running on chain: ", vm.toString(block.chainid)); + console.log("Executing from address", msg.sender); + + deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + _walletFactory = KintoWalletFactory(payable(_getChainDeployment("KintoWalletFactory"))); + } + + /// @dev deploys contracts from deployer and upgrades them using LEDGER_ADMIN + function deployAndUpgrade(string memory contractName, string memory version, bytes memory bytecode) public { + bool isWallet = keccak256(abi.encodePacked(contractName)) == keccak256(abi.encodePacked("KintoWallet")); + address proxy = _getChainDeployment(contractName); + + if (!isWallet) require(proxy != address(0), "Need to execute main deploy script first"); + + // (1). deploy new implementation via wallet factory + vm.broadcast(deployerPrivateKey); + address _impl = _walletFactory.deployContract(vm.envAddress("LEDGER_ADMIN"), 0, bytecode, bytes32(0)); + + console.log(string.concat(contractName, version, "-impl: ", vm.toString(address(_impl)))); + + // (2). call upgradeTo to set new implementation + if (isWallet) { + vm.broadcast(); // requires LEDGER_ADMIN + // vm.prank(vm.envAddress("LEDGER_ADMIN")); + _walletFactory.upgradeAllWalletImplementations(IKintoWallet(_impl)); + } else { + vm.broadcast(); // requires LEDGER_ADMIN + // vm.prank(vm.envAddress("LEDGER_ADMIN")); + UUPSUpgradeable(proxy).upgradeTo(_impl); + } + } + + // utils for doing actions through EntryPoint + + function _upgradeTo(address _proxy, address _newImpl, uint256 _signerPk) internal { + address payable _from = payable(_getChainDeployment("KintoWallet-admin")); + + // prep upgradeTo user op + uint256 nonce = IKintoWallet(_from).getNonce(); + uint256[] memory privateKeys = new uint256[](1); + privateKeys[0] = _signerPk; + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( + block.chainid, + _from, + _proxy, + 0, + nonce, + privateKeys, + abi.encodeWithSelector(UUPSUpgradeable.upgradeTo.selector, address(_newImpl)), + _getChainDeployment("SponsorPaymaster") + ); + + vm.broadcast(deployerPrivateKey); + IEntryPoint(_getChainDeployment("EntryPoint")).handleOps(userOps, payable(vm.addr(_signerPk))); + } + + function _initialize(address _proxy, uint256 _signerPk) internal { + address payable _from = payable(_getChainDeployment("KintoWallet-admin")); + + // fund _proxy in the paymaster + require( + ISponsorPaymaster(payable(_getChainDeployment("SponsorPaymaster"))).balances(_proxy) > 0, + "Need to fund proxy in paymaster" + ); + + // todo: move to a fund function + // ISponsorPaymaster _paymaster = ISponsorPaymaster(_getChainDeployment("SponsorPaymaster")); + // vm.broadcast(deployerPrivateKey); + // _paymaster.addDepositFor{value: 0.00000001 ether}(_proxy); + // assertEq(_paymaster.balances(_proxy), 0.00000001 ether); + + // prep upgradeTo user op + uint256 nonce = IKintoWallet(_from).getNonce(); + uint256[] memory privateKeys = new uint256[](1); + privateKeys[0] = _signerPk; + + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( + block.chainid, + _from, + _proxy, + 0, + nonce, + privateKeys, + abi.encodeWithSelector(IInitialize.initialize.selector), + _getChainDeployment("SponsorPaymaster") + ); + + vm.broadcast(deployerPrivateKey); + IEntryPoint(_getChainDeployment("EntryPoint")).handleOps(userOps, payable(vm.addr(_signerPk))); + } + + function _transferOwnership(address _proxy, uint256 _signerPk, address _newOwner) internal { + address payable _from = payable(_getChainDeployment("KintoWallet-admin")); + + // prep upgradeTo user op + uint256 nonce = IKintoWallet(_from).getNonce(); + uint256[] memory privateKeys = new uint256[](1); + privateKeys[0] = _signerPk; + + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( + block.chainid, + _from, + _proxy, + 0, + nonce, + privateKeys, + abi.encodeWithSelector(Ownable.transferOwnership.selector, _newOwner), + _getChainDeployment("SponsorPaymaster") + ); + + vm.broadcast(deployerPrivateKey); + IEntryPoint(_getChainDeployment("EntryPoint")).handleOps(userOps, payable(vm.addr(_signerPk))); + } +} diff --git a/script/test.sol b/script/test.sol index 9624d79c3..b3a5fa8a1 100644 --- a/script/test.sol +++ b/script/test.sol @@ -42,7 +42,7 @@ contract KintoDeployTestWalletScript is AASetup, KYCSignature { vm.startBroadcast(deployerPrivateKey); if (!_kintoID.isKYC(recipientWallet)) { IKintoID.SignatureData memory sigdata = - _auxCreateSignature(_kintoID, recipientWallet, recipientWallet, recipientKey, block.timestamp + 1000); + _auxCreateSignature(_kintoID, recipientWallet, recipientKey, block.timestamp + 1000); uint16[] memory traits = new uint16[](0); _kintoID.mintIndividualKyc(sigdata, traits); } @@ -156,7 +156,6 @@ contract KintoDeployTestCounter is AASetup, KYCSignature, UserOp { ); UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = userOp; - // Execute the transaction via the entry point _entryPoint.handleOps(userOps, payable(deployerPublicKey)); console.log("After UserOp. Counter:", counter.count()); vm.stopBroadcast(); @@ -234,7 +233,6 @@ contract KintoDeployETHPriceIsRight is AASetup, KYCSignature, UserOp { ); UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = userOp; - // Execute the transaction via the entry point _entryPoint.handleOps(userOps, payable(deployerPublicKey)); console.log("After UserOp. ETHPriceIsRight guess count", ethpriceisright.guessCount()); console.log("After UserOp. ETHPriceIsRight avg guess", ethpriceisright.avgGuess()); diff --git a/script/upgrade.sol b/script/upgrade.sol index 53918a09a..f7bbcc7eb 100644 --- a/script/upgrade.sol +++ b/script/upgrade.sol @@ -39,8 +39,8 @@ contract KintoWalletFactoryUpgradeScript is ArtifactsReader { } contract KintoWalletVTest is KintoWallet { - constructor(IEntryPoint _entryPoint, IKintoID _kintoIDv1, IKintoAppRegistry _kintoApp) - KintoWallet(_entryPoint, _kintoIDv1, _kintoApp) + constructor(IEntryPoint _entryPoint, IKintoID _kintoID, IKintoAppRegistry _kintoApp) + KintoWallet(_entryPoint, _kintoID, _kintoApp) {} } diff --git a/src/Faucet.sol b/src/Faucet.sol index 07cb5c7bc..1fcdb2b6e 100644 --- a/src/Faucet.sol +++ b/src/Faucet.sol @@ -19,9 +19,11 @@ contract Faucet is Initializable, UUPSUpgradeable, OwnableUpgradeable, IFaucet { using SignatureChecker for address; /* ============ Events ============ */ + event Claim(address indexed _to, uint256 _timestamp); /* ============ Constants ============ */ + uint256 public constant CLAIM_AMOUNT = 1 ether / 2500; uint256 public constant FAUCET_AMOUNT = 1 ether; diff --git a/src/paymasters/SponsorPaymaster.sol b/src/paymasters/SponsorPaymaster.sol index c7b6fd3a8..10d146a4f 100644 --- a/src/paymasters/SponsorPaymaster.sol +++ b/src/paymasters/SponsorPaymaster.sol @@ -277,25 +277,6 @@ contract SponsorPaymaster is Initializable, BasePaymaster, UUPSUpgradeable, Reen ); } - /// Return app's sponsor from the registry - /// @return sponsor - the sponsor address - /// @dev reverts if neither execute nor executeBatch - /// @dev ensures all targets are sponsored by the same app if executeBatch (todo: decouple this) - function _getSponsor(bytes calldata callData) internal view returns (address sponsor) { - bytes4 selector = bytes4(callData[:4]); - if (selector == IKintoWallet.execute.selector) { - (address target,,) = abi.decode(callData[4:], (address, uint256, bytes)); - sponsor = appRegistry.getSponsor(target); - } else if (selector == IKintoWallet.executeBatch.selector) { - // last target is the sponsor - (address[] memory targets,,) = abi.decode(callData[4:], (address[], uint256[], bytes[])); - sponsor = appRegistry.getSponsor(targets[targets.length - 1]); - } else { - // handle unknown function or error - revert("SP: Unknown function selector"); - } - } - // @notice extracts `target` contract from callData // @dev the last op on a batch MUST always be a contract whose sponsor is the one we want to // bear with the gas cost of all ops diff --git a/src/sample/CounterInitializable.sol b/src/sample/InitializableCounter.sol similarity index 91% rename from src/sample/CounterInitializable.sol rename to src/sample/InitializableCounter.sol index eb78623c6..e9603489b 100644 --- a/src/sample/CounterInitializable.sol +++ b/src/sample/InitializableCounter.sol @@ -5,7 +5,7 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -contract CounterInitializable is Initializable, OwnableUpgradeable, UUPSUpgradeable { +contract InitializableCounter is Initializable, OwnableUpgradeable, UUPSUpgradeable { /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); diff --git a/src/wallet/KintoWallet.sol b/src/wallet/KintoWallet.sol index 3cc8a2057..b4a114526 100644 --- a/src/wallet/KintoWallet.sol +++ b/src/wallet/KintoWallet.sol @@ -111,7 +111,7 @@ contract KintoWallet is Initializable, BaseAccount, TokenCallbackHandler, IKinto for (uint256 i = 0; i < dest.length; i++) { _executeInner(dest[i], values[i], func[i]); } - // If can transact, cancel recovery + // if can transact, cancel recovery inRecovery = 0; } @@ -335,6 +335,7 @@ contract KintoWallet is Initializable, BaseAccount, TokenCallbackHandler, IKinto return appRegistry.isSponsored(sponsor, target) || appRegistry.childToParentContract(target) == sponsor; } + // @notice ensures signer has signed the hash function _verifySingleSignature(address signer, bytes32 hashData, bytes memory signature) private pure @@ -346,6 +347,7 @@ contract KintoWallet is Initializable, BaseAccount, TokenCallbackHandler, IKinto return _packValidationData(false, 0, 0); } + // @notice ensures required signers have signed the hash function _verifyMultipleSignatures(bytes32 hashData, bytes memory signature) private view returns (uint256) { // calculate required signers uint256 requiredSigners = @@ -371,6 +373,8 @@ contract KintoWallet is Initializable, BaseAccount, TokenCallbackHandler, IKinto return _packValidationData(requiredSigners != 0, 0, 0); } + // @dev SINGLE_SIGNER policy expects the wallet to have only one owner though this is not enforced. + // Any "extra" owners won't be considered when validating the signature. function _resetSigners(address[] calldata newSigners, uint8 _policy) internal { require(newSigners.length > 0 && newSigners.length <= MAX_SIGNERS, "KW-rs: invalid array"); require(newSigners[0] != address(0) && kintoID.isKYC(newSigners[0]), "KW-rs: KYC Required"); @@ -387,7 +391,8 @@ contract KintoWallet is Initializable, BaseAccount, TokenCallbackHandler, IKinto } owners = newSigners; emit SignersChanged(newSigners, owners); - // Change policy if needed. + + // change policy if needed. if (_policy != signerPolicy) { setSignerPolicy(_policy); } else { diff --git a/test/DeployTests.t.sol b/test/DeployTests.t.sol index 81b6093a5..d2075eec4 100644 --- a/test/DeployTests.t.sol +++ b/test/DeployTests.t.sol @@ -4,47 +4,41 @@ pragma solidity ^0.8.18; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import "../src/sample/CounterInitializable.sol"; -import {OwnableCounter as Counter} from "../src/sample/OwnableCounter.sol"; - -import "./helpers/UserOp.sol"; +import "../src/sample/OwnableCounter.sol"; +import "../src/sample/InitializableCounter.sol"; +import "./SharedSetup.t.sol"; import "./helpers/UUPSProxy.sol"; -import {AATestScaffolding} from "./helpers/AATestScaffolding.sol"; - -contract DeveloperDeployTest is UserOp, AATestScaffolding { - uint256 _chainID = 1; - UUPSProxy _proxyc; - Counter _counter; - CounterInitializable _counterInit; +contract DeveloperDeployTest is SharedSetup { + OwnableCounter _ownableCounter; + InitializableCounter _initializableCounter; - function setUp() public { - vm.chainId(_chainID); - vm.startPrank(address(1)); - _owner.transfer(1e18); - vm.stopPrank(); - deployAAScaffolding(_owner, 1, _kycProvider, _recoverer); - vm.startPrank(_owner); + function setUp() public override { + super.setUp(); - address created = - _walletFactory.deployContract(_owner, 0, abi.encodePacked(type(Counter).creationCode), bytes32(0)); - _counter = Counter(created); + // deploy ownable counter + vm.prank(_owner); + _ownableCounter = OwnableCounter( + _walletFactory.deployContract(_owner, 0, abi.encodePacked(type(OwnableCounter).creationCode), bytes32(0)) + ); - created = _walletFactory.deployContract( - _owner, 0, abi.encodePacked(type(CounterInitializable).creationCode), bytes32(0) + // deploy initialisable counter + vm.prank(_owner); + address _implementation = _walletFactory.deployContract( + _owner, 0, abi.encodePacked(type(InitializableCounter).creationCode), bytes32(0) ); - // deploy _proxy contract and point it to _implementation - _proxyc = new UUPSProxy{salt: 0}(address(created), ""); - // wrap in ABI to support easier calls - _counterInit = CounterInitializable(address(_proxyc)); - // Initialize proxy - _counterInit.initialize(_user2); - vm.stopPrank(); + // deploy proxy contract for initialisable counter and point it to _implementation + UUPSProxy _proxy = new UUPSProxy{salt: 0}(address(_implementation), ""); + + // initialize proxy + _initializableCounter = InitializableCounter(address(_proxy)); + vm.prank(_owner); + _initializableCounter.initialize(_user2); } - function testUp() public { - assertEq(address(_owner), _counter.owner()); - assertEq(_user2, _counterInit.owner()); + function testUp() public override { + assertEq(address(_owner), _ownableCounter.owner()); + assertEq(_user2, _initializableCounter.owner()); } } diff --git a/test/ETHPriceIsRight.t.sol b/test/ETHPriceIsRight.t.sol index 1a838e6ce..9039e00a1 100644 --- a/test/ETHPriceIsRight.t.sol +++ b/test/ETHPriceIsRight.t.sol @@ -28,7 +28,7 @@ contract ETHPriceIsRightTest is Test { assertEq(_priceIsRight.maxGuess(), 0); } - // Upgrade Tests + // Upgrade tests function testAnyoneCanEnterGuessBeforeEnd() public { vm.startPrank(_user); diff --git a/test/EngenCredits.t.sol b/test/EngenCredits.t.sol index d63ad34fb..404a096f0 100644 --- a/test/EngenCredits.t.sol +++ b/test/EngenCredits.t.sol @@ -4,12 +4,8 @@ pragma solidity ^0.8.18; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import "@aa/core/EntryPoint.sol"; - import "../src/tokens/EngenCredits.sol"; - -import "./helpers/UserOp.sol"; -import {AATestScaffolding} from "./helpers/AATestScaffolding.sol"; +import "./SharedSetup.t.sol"; contract EngenCreditsV2 is EngenCredits { function newFunction() external pure returns (uint256) { @@ -19,69 +15,135 @@ contract EngenCreditsV2 is EngenCredits { constructor() EngenCredits() {} } -contract EngenCreditsTest is UserOp, AATestScaffolding { - EngenCreditsV2 _engenCreditsV2; - - uint256 _chainID = 1; - - function setUp() public { - vm.chainId(1); - vm.startPrank(address(1)); - _owner.transfer(1e18); - vm.stopPrank(); - deployAAScaffolding(_owner, 1, _kycProvider, _recoverer); +contract EngenCreditsTest is SharedSetup { + function setUp() public override { + super.setUp(); fundSponsorForApp(address(_engenCredits)); - fundSponsorForApp(address(_kintoWallet)); - vm.startPrank(_owner); - _kintoAppRegistry.registerApp( - "engen credits", address(_engenCredits), new address[](0), [uint256(0), uint256(0), uint256(0), uint256(0)] - ); - vm.stopPrank(); + registerApp(_owner, "engen credits", address(_engenCredits)); } - function testUp() public { + function testUp() public override { + super.testUp(); assertEq(_engenCredits.totalSupply(), 0); assertEq(_engenCredits.owner(), _owner); assertEq(_engenCredits.transfersEnabled(), false); assertEq(_engenCredits.burnsEnabled(), false); } - /* ============ Upgrade Tests ============ */ + /* ============ Upgrade tests ============ */ - function testOwnerCanUpgradeEngen() public { + function testUpgradeTo() public { vm.startPrank(_owner); EngenCreditsV2 _implementationV2 = new EngenCreditsV2(); _engenCredits.upgradeTo(address(_implementationV2)); // ensure that the implementation has been upgraded - _engenCreditsV2 = EngenCreditsV2(address(_engenCredits)); + EngenCreditsV2 _engenCreditsV2 = EngenCreditsV2(address(_engenCredits)); assertEq(_engenCreditsV2.newFunction(), 1); vm.stopPrank(); } - function test_RevertWhen_OthersCannotUpgrade() public { + function testUpgradeTo_RevertWhen_WhenCallerIsNotOwner() public { EngenCreditsV2 _implementationV2 = new EngenCreditsV2(); vm.expectRevert("Ownable: caller is not the owner"); _engenCredits.upgradeTo(address(_implementationV2)); } - /* ============ Token Tests ============ */ + /* ============ Set Transfer Enabled tests ============ */ + + function testSetTransferEnabled() public { + assertTrue(!_engenCredits.transfersEnabled()); + vm.prank(_owner); + _engenCredits.setTransfersEnabled(true); + assertTrue(_engenCredits.transfersEnabled()); + } + + function testSetTransferEnabled_RevertWhen_CallerIsNotOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + _engenCredits.setTransfersEnabled(true); + } + + function testSetTransferEnabled_RevertWhen_AlreadyEnabled() public { + vm.prank(_owner); + _engenCredits.setTransfersEnabled(true); + + vm.expectRevert("EC: Transfers Already enabled"); + vm.prank(_owner); + _engenCredits.setTransfersEnabled(true); + } + + function testSetTransferEnabled_WhenDisabling() public { + vm.prank(_owner); + _engenCredits.setTransfersEnabled(false); + assertEq(_engenCredits.transfersEnabled(), false); + } + + function testSetTransferEnabled_WhenDisablingAndEnabling() public { + vm.prank(_owner); + _engenCredits.setTransfersEnabled(false); + assertEq(_engenCredits.transfersEnabled(), false); + + vm.prank(_owner); + _engenCredits.setTransfersEnabled(true); + assertEq(_engenCredits.transfersEnabled(), true); + } + + /* ============ Set Burns Enabled tests ============ */ + + function testSetBurnsEnabled() public { + assertTrue(!_engenCredits.burnsEnabled()); + vm.prank(_owner); + _engenCredits.setBurnsEnabled(true); + assertTrue(_engenCredits.burnsEnabled()); + } - function testOwnerCanMint() public { + function testSetBurnsEnabled_RevertWhen_CallerIsNotOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + _engenCredits.setBurnsEnabled(true); + } + + function testSetBurnsEnabled_RevertWhen_AlreadyEnabled() public { + vm.prank(_owner); + _engenCredits.setBurnsEnabled(true); + + vm.expectRevert("EC: Burns Already enabled"); + vm.prank(_owner); + _engenCredits.setBurnsEnabled(true); + } + + function testSetBurnsEnabled_WhenDisabling() public { + vm.prank(_owner); + _engenCredits.setBurnsEnabled(false); + assertEq(_engenCredits.burnsEnabled(), false); + } + + function testSetBurnsEnabled_WhenDisablingAndEnabling() public { + vm.prank(_owner); + _engenCredits.setBurnsEnabled(false); + assertEq(_engenCredits.burnsEnabled(), false); + + vm.prank(_owner); + _engenCredits.setBurnsEnabled(true); + assertEq(_engenCredits.burnsEnabled(), true); + } + + /* ============ Token tests ============ */ + + function testMint() public { vm.startPrank(_owner); _engenCredits.mint(_user, 100); assertEq(_engenCredits.balanceOf(_user), 100); vm.stopPrank(); } - function testOthersCannotMint() public { + function testMint_RevertWhen_CallerIsNotOwner() public { vm.expectRevert("Ownable: caller is not the owner"); _engenCredits.mint(_user, 100); assertEq(_engenCredits.balanceOf(_user), 0); } - function testNobodyCanTransfer() public { + function testTransfer_RevertWhen_CallerIsAnyone() public { vm.startPrank(_owner); _engenCredits.mint(_owner, 100); vm.expectRevert("EC: Transfers not enabled"); @@ -89,7 +151,7 @@ contract EngenCreditsTest is UserOp, AATestScaffolding { vm.stopPrank(); } - function testNobodyCanBurn() public { + function testBurn_RevertWhen_CallerIsAnyone() public { vm.startPrank(_owner); _engenCredits.mint(_user, 100); vm.expectRevert("EC: Transfers not enabled"); @@ -97,123 +159,201 @@ contract EngenCreditsTest is UserOp, AATestScaffolding { vm.stopPrank(); } - function testCanTransferAfterSettingFlag() public { + function testTransfer_WhenTransfersAreEnabled() public { vm.startPrank(_owner); + _engenCredits.mint(_owner, 100); _engenCredits.setTransfersEnabled(true); _engenCredits.transfer(_user2, 100); + assertEq(_engenCredits.balanceOf(_user2), 100); assertEq(_engenCredits.balanceOf(_owner), 0); + vm.stopPrank(); } - function testCanBurnAfterSettingFlag() public { + function testBurn_WhenBurnsAreEnabled() public { vm.startPrank(_owner); + _engenCredits.mint(_owner, 100); _engenCredits.setBurnsEnabled(true); _engenCredits.burn(100); + assertEq(_engenCredits.balanceOf(_owner), 0); + vm.stopPrank(); } - /* ============ Engen Tests ============ */ + /* ============ Phase Override tests ============ */ - function testWalletCanGetPoints() public { + function testSetPhase1Override() public { + assertEq(_engenCredits.phase1Override(address(_kintoWallet)), 0); + + uint256[] memory points = new uint256[](1); + points[0] = 10; + address[] memory addresses = new address[](1); + addresses[0] = address(_kintoWallet); + + vm.prank(_owner); + _engenCredits.setPhase1Override(addresses, points); + + assertEq(_engenCredits.phase1Override(address(_kintoWallet)), 10); + } + + function testSetPhase1Override_RevertWhen_LengthMismatch() public { + uint256[] memory points = new uint256[](2); + points[0] = 10; + points[1] = 10; + + address[] memory addresses = new address[](1); + addresses[0] = address(_kintoWallet); + + vm.prank(_owner); + vm.expectRevert("EC: Invalid input"); + _engenCredits.setPhase1Override(addresses, points); + } + + /* ============ Mint Credits tests ============ */ + + function testMintCredits() public { assertEq(_engenCredits.balanceOf(address(_kintoWallet)), 0); assertEq(_engenCredits.calculatePoints(address(_kintoWallet)), 15); - vm.startPrank(_owner); - // Let's send a transaction to the counter contract through our wallet - uint256 nonce = _kintoWallet.getNonce(); - uint256[] memory privateKeys = new uint256[](1); - privateKeys[0] = 1; - UserOperation memory userOp = _createUserOperation( + + whitelistApp(address(_engenCredits)); + + // mint credit + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( address(_kintoWallet), address(_engenCredits), - nonce + 1, + _kintoWallet.getNonce(), privateKeys, abi.encodeWithSignature("mintCredits()"), address(_paymaster) ); - UserOperation[] memory userOps = new UserOperation[](2); - userOps[0] = _whitelistAppOp( - privateKeys, address(_kintoWallet), _kintoWallet.getNonce(), address(_engenCredits), address(_paymaster) - ); - userOps[1] = userOp; - // Execute the transaction via the entry point + _entryPoint.handleOps(userOps, payable(_owner)); assertEq(_engenCredits.balanceOf(address(_kintoWallet)), 15); - vm.stopPrank(); } - function testWalletCanGetPointsWithOverride() public { - vm.startPrank(_owner); + function testMintCredits_whenOverride() public { + whitelistApp(address(_engenCredits)); + + // set phase override uint256[] memory points = new uint256[](1); points[0] = 10; address[] memory addresses = new address[](1); addresses[0] = address(_kintoWallet); + vm.prank(_owner); _engenCredits.setPhase1Override(addresses, points); + assertEq(_engenCredits.balanceOf(address(_kintoWallet)), 0); assertEq(_engenCredits.calculatePoints(address(_kintoWallet)), 20); - // Let's send a transaction to the counter contract through our wallet - uint256 nonce = _kintoWallet.getNonce(); - uint256[] memory privateKeys = new uint256[](1); - privateKeys[0] = 1; - UserOperation memory userOp = _createUserOperation( + + // mint credits + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( address(_kintoWallet), address(_engenCredits), - nonce + 1, + _kintoWallet.getNonce(), privateKeys, abi.encodeWithSignature("mintCredits()"), address(_paymaster) ); - UserOperation[] memory userOps = new UserOperation[](2); - userOps[0] = _whitelistAppOp( - privateKeys, address(_kintoWallet), _kintoWallet.getNonce(), address(_engenCredits), address(_paymaster) - ); - userOps[1] = userOp; - // Execute the transaction via the entry point _entryPoint.handleOps(userOps, payable(_owner)); assertEq(_engenCredits.balanceOf(address(_kintoWallet)), 20); - vm.stopPrank(); } - function testWalletCannotGetPointsTwice() public { + function testMintCredits_WhenCalledTwice() public { + whitelistApp(address(_engenCredits)); + assertEq(_engenCredits.balanceOf(address(_kintoWallet)), 0); assertEq(_engenCredits.calculatePoints(address(_kintoWallet)), 15); - vm.startPrank(_owner); - // Let's send a transaction to the counter contract through our wallet - uint256 nonce = _kintoWallet.getNonce(); - uint256[] memory privateKeys = new uint256[](1); - privateKeys[0] = 1; - UserOperation memory userOp = _createUserOperation( + + // mint creidts + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( address(_kintoWallet), address(_engenCredits), - nonce + 1, + _kintoWallet.getNonce(), privateKeys, abi.encodeWithSignature("mintCredits()"), address(_paymaster) ); - UserOperation[] memory userOps = new UserOperation[](2); - userOps[0] = _whitelistAppOp( - privateKeys, address(_kintoWallet), _kintoWallet.getNonce(), address(_engenCredits), address(_paymaster) - ); - userOps[1] = userOp; - // Execute the transaction via the entry point + _entryPoint.handleOps(userOps, payable(_owner)); assertEq(_engenCredits.balanceOf(address(_kintoWallet)), 15); + // call again - userOp = _createUserOperation( + userOps[0] = _createUserOperation( address(_kintoWallet), address(_engenCredits), - nonce + 2, + _kintoWallet.getNonce(), privateKeys, abi.encodeWithSignature("mintCredits()"), address(_paymaster) ); - userOps = new UserOperation[](1); - userOps[0] = userOp; + + vm.expectEmit(true, true, true, false); + emit UserOperationRevertReason( + _entryPoint.getUserOpHash(userOps[0]), userOps[0].sender, userOps[0].nonce, bytes("") + ); + vm.recordLogs(); _entryPoint.handleOps(userOps, payable(_owner)); + assertRevertReasonEq("EC: No tokens to mint"); + assertEq(_engenCredits.balanceOf(address(_kintoWallet)), 15); - vm.stopPrank(); + } + + function testMintCredits_RevertWhen_TransfersEnabled() public { + whitelistApp(address(_engenCredits)); + + vm.prank(_owner); + _engenCredits.setTransfersEnabled(true); + + // mint credit + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( + address(_kintoWallet), + address(_engenCredits), + _kintoWallet.getNonce(), + privateKeys, + abi.encodeWithSignature("mintCredits()"), + address(_paymaster) + ); + + vm.expectEmit(true, true, true, false); + emit UserOperationRevertReason( + _entryPoint.getUserOpHash(userOps[0]), userOps[0].sender, userOps[0].nonce, bytes("") + ); + vm.recordLogs(); + _entryPoint.handleOps(userOps, payable(_owner)); + assertRevertReasonEq("EC: Mint not allowed after completion"); + } + + function testMintCredits_RevertWhen_BurnsEnabled() public { + whitelistApp(address(_engenCredits)); + + vm.prank(_owner); + _engenCredits.setBurnsEnabled(true); + + // mint credit + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( + address(_kintoWallet), + address(_engenCredits), + _kintoWallet.getNonce(), + privateKeys, + abi.encodeWithSignature("mintCredits()"), + address(_paymaster) + ); + + vm.expectEmit(true, true, true, false); + emit UserOperationRevertReason( + _entryPoint.getUserOpHash(userOps[0]), userOps[0].sender, userOps[0].nonce, bytes("") + ); + vm.recordLogs(); + _entryPoint.handleOps(userOps, payable(_owner)); + assertRevertReasonEq("EC: Mint not allowed after completion"); } } diff --git a/test/Faucet.t.sol b/test/Faucet.t.sol index 18b86ff22..fec047899 100644 --- a/test/Faucet.t.sol +++ b/test/Faucet.t.sol @@ -8,11 +8,10 @@ import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "../src/interfaces/IFaucet.sol"; import "../src/Faucet.sol"; -import "./helpers/UserOp.sol"; import "./helpers/UUPSProxy.sol"; -import {AATestScaffolding} from "./helpers/AATestScaffolding.sol"; +import "./SharedSetup.t.sol"; -contract FaucetV999 is Faucet { +contract FaucetNewUpgrade is Faucet { function newFunction() external pure returns (uint256) { return 1; } @@ -20,89 +19,48 @@ contract FaucetV999 is Faucet { constructor(address _kintoWalletFactory) Faucet(_kintoWalletFactory) {} } -contract FaucetTest is UserOp, AATestScaffolding { +contract FaucetTest is SharedSetup { using ECDSA for bytes32; - UUPSProxy _proxyFaucet; - Faucet _implFaucet; - FaucetV999 _implFaucetV999; - Faucet _faucet; - FaucetV999 _FaucetV999; - - // Create a aux function to create an EIP-191 comploiant signature for claiming Kinto ETH from the faucet - function _auxCreateSignature(address _signer, uint256 _privateKey, uint256 _expiresAt) - private - view - returns (IFaucet.SignatureData memory signData) - { - bytes32 dataHash = keccak256( - abi.encode(_signer, address(_faucet), _expiresAt, _faucet.nonces(_signer), bytes32(block.chainid)) - ); - bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)); // EIP-191 compliant - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(_privateKey, ethSignedMessageHash); - bytes memory signature = abi.encodePacked(r, s, v); - return IFaucet.SignatureData(_signer, _faucet.nonces(_signer), _expiresAt, signature); - } - - function setUp() public { - vm.chainId(block.chainid); - vm.startPrank(address(1)); - _owner.transfer(1e18); - vm.stopPrank(); - deployAAScaffolding(_owner, 1, _kycProvider, _recoverer); - vm.startPrank(_owner); - _implFaucet = new Faucet{salt: 0}(address(_walletFactory)); - // deploy _proxy contract and point it to _implementation - _proxyFaucet = new UUPSProxy{salt: 0}(address(_implFaucet), ""); - // wrap in ABI to support easier calls - _faucet = Faucet(payable(address(_proxyFaucet))); - // Initialize kyc viewer _proxy - _faucet.initialize(); - vm.stopPrank(); - } - - function testUp() public { + function testUp() public override { + super.testUp(); assertEq(_faucet.CLAIM_AMOUNT(), 1 ether / 2500); assertEq(_faucet.FAUCET_AMOUNT(), 1 ether); } - /* ============ Upgrade Tests ============ */ + /* ============ Upgrade tests ============ */ - function testOwnerCanUpgradeViewer() public { - vm.startPrank(_owner); - FaucetV999 _implementationV2 = new FaucetV999(address(_walletFactory)); - _faucet.upgradeTo(address(_implementationV2)); - // re-wrap the _proxy - _FaucetV999 = FaucetV999(payable(address(_faucet))); - assertEq(_FaucetV999.newFunction(), 1); - vm.stopPrank(); + function testUpgradeTo() public { + FaucetNewUpgrade _newImpl = new FaucetNewUpgrade(address(_walletFactory)); + vm.prank(_owner); + _faucet.upgradeTo(address(_newImpl)); + + assertEq(FaucetNewUpgrade(payable(address(_faucet))).newFunction(), 1); } - function test_RevertWhen_OthersCannotUpgradeFactory() public { - FaucetV999 _implementationV2 = new FaucetV999(address(_walletFactory)); + function testUpgradeTo_RevertWhen_CallerIsNotOwner() public { + FaucetNewUpgrade _newImpl = new FaucetNewUpgrade(address(_walletFactory)); + vm.expectRevert("only owner"); - _faucet.upgradeTo(address(_implementationV2)); + _faucet.upgradeTo(address(_newImpl)); } - /* ============ Claim Tests ============ */ + /* ============ Start Faucet tests ============ */ - function testOwnerCanStartFaucet() public { - vm.startPrank(_owner); + function testStartFaucet() public { + vm.prank(_owner); _faucet.startFaucet{value: 1 ether}(); assertEq(address(_faucet).balance, _faucet.FAUCET_AMOUNT()); - - vm.stopPrank(); } - function testStart_RevertWhen_OwnerCannotStartWithoutAmount(uint256 amt) public { + function testStartFaucet_RevertWhen_AmountIsLess(uint256 amt) public { vm.assume(amt < _faucet.FAUCET_AMOUNT()); vm.prank(_owner); vm.expectRevert("Not enough ETH to start faucet"); _faucet.startFaucet{value: amt}(); } - function testStart_RevertWhen_CallerIsNotOwner(address someone) public { + function testStartFaucet_RevertWhen_CallerIsNotOwner(address someone) public { vm.assume(someone != _faucet.owner()); vm.deal(someone, 1 ether); vm.prank(someone); @@ -110,19 +68,38 @@ contract FaucetTest is UserOp, AATestScaffolding { _faucet.startFaucet{value: 1 ether}(); } - function testClaim() public { - vm.startPrank(_owner); + /* ============ Claim From Faucet tests ============ */ + + function testClaimFromFaucet_WhenCallingOnBehalf() public { + IFaucet.SignatureData memory sigdata = _auxCreateSignature(_faucet, _user, _userPk, block.timestamp + 1000); + + vm.prank(_owner); _faucet.startFaucet{value: 1 ether}(); - vm.stopPrank(); - vm.startPrank(_user); + assertEq(_faucet.claimed(_user), false); + assertEq(_faucet.nonces(_user), 0); + + vm.prank(_owner); + _walletFactory.claimFromFaucet(address(_faucet), sigdata); + assertEq(_faucet.claimed(_user), true); + assertEq(_faucet.nonces(_user), 1); + } + + /* ============ Claim Kinto ETH tests ============ */ + + function testClaimKintoETH1() public { + console.log(address(_faucet)); + vm.prank(_owner); + _faucet.startFaucet{value: 1 ether}(); + uint256 previousBalance = address(_user).balance; + vm.prank(_user); _faucet.claimKintoETH(); + assertEq(address(_faucet).balance, 1 ether - _faucet.CLAIM_AMOUNT()); assertEq(address(_user).balance, previousBalance + _faucet.CLAIM_AMOUNT()); - vm.stopPrank(); } - function testClaim_RevertWhen_ClaimedTwice() public { + function testClaimKintoETH_RevertWhen_ClaimTwice() public { vm.prank(_owner); _faucet.startFaucet{value: 1 ether}(); @@ -134,26 +111,11 @@ contract FaucetTest is UserOp, AATestScaffolding { vm.stopPrank(); } - function testClaimOnBehalf() public { - IFaucet.SignatureData memory sigdata = _auxCreateSignature(_user, 3, block.timestamp + 1000); - vm.startPrank(_owner); - - _faucet.startFaucet{value: 1 ether}(); - assertEq(_faucet.claimed(_user), false); - assertEq(_faucet.nonces(_user), 0); - - _walletFactory.claimFromFaucet(address(_faucet), sigdata); - assertEq(_faucet.claimed(_user), true); - assertEq(_faucet.nonces(_user), 1); - - vm.stopPrank(); - } - - function testClaimOnBehalf_RevertWhen_CallerIsNotFactory() public { + function testClaimKintoETH_RevertWhen_CallerIsNotFactory() public { vm.prank(_owner); _faucet.startFaucet{value: 1 ether}(); - IFaucet.SignatureData memory sigdata = _auxCreateSignature(_user, 3, block.timestamp + 1000); + IFaucet.SignatureData memory sigdata = _auxCreateSignature(_faucet, _user, _userPk, block.timestamp + 1000); vm.expectRevert("Only wallet factory can call this"); _faucet.claimKintoETH(sigdata); } @@ -164,21 +126,22 @@ contract FaucetTest is UserOp, AATestScaffolding { _faucet.claimKintoETH(); } - // fixme: makes foundry to hang - // function testClaim_DeactivatesWhenNotEnoughBalanceForNextClaim() public { - // vm.prank(_owner); - // _faucet.startFaucet{value: 1 ether}(); + function testClaim_DeactivatesWhenNotEnoughBalanceForNextClaim() public { + vm.prank(_owner); + _faucet.startFaucet{value: 1 ether}(); + + // reduce faucet balance to CLAIM AMOUNT + vm.deal(address(_faucet), _faucet.CLAIM_AMOUNT()); + + vm.prank(_user); + _faucet.claimKintoETH(); - // for (uint256 i = 1; i <= 2500; i++) { - // vm.prank(vm.addr(i)); - // _faucet.claimKintoETH(); - // } - // // assert faucet is deactivated - // assertEq(address(_faucet).balance, 0); - // assertEq(_faucet.active(), false); - // } + // assert faucet is deactivated + assertEq(address(_faucet).balance, 0); + assertEq(_faucet.active(), false); + } - /* ============ Withdraw Tests ============ */ + /* ============ Withdraw tests ============ */ function testWithdrawAll() public { vm.startPrank(_owner); @@ -201,12 +164,14 @@ contract FaucetTest is UserOp, AATestScaffolding { _faucet.withdrawAll(); } - /* ============ Top up Tests ============ */ + /* ============ Top up tests ============ */ - function testSignerCanFundFaucet() public { - vm.startPrank(_owner); + function testSendMoneyToAccount() public { uint256 balanceBefore = address(_faucet).balance; + + vm.prank(_owner); _walletFactory.sendMoneyToAccount{value: 1e15}(address(_faucet)); + assertEq(address(_faucet).balance, balanceBefore + 1e15); } } diff --git a/test/KYCViewer.t.sol b/test/KYCViewer.t.sol index fcf5e6c72..80763a993 100644 --- a/test/KYCViewer.t.sol +++ b/test/KYCViewer.t.sol @@ -9,9 +9,8 @@ import "../src/wallet/KintoWalletFactory.sol"; import "../src/KintoID.sol"; import "../src/viewers/KYCViewer.sol"; -import "./helpers/UserOp.sol"; +import "./SharedSetup.t.sol"; import "./helpers/UUPSProxy.sol"; -import {AATestScaffolding} from "./helpers/AATestScaffolding.sol"; contract KYCViewerUpgraded is KYCViewer { function newFunction() external pure returns (uint256) { @@ -21,64 +20,39 @@ contract KYCViewerUpgraded is KYCViewer { constructor(address _kintoWalletFactory) KYCViewer(_kintoWalletFactory) {} } -contract KYCViewerTest is UserOp, AATestScaffolding { - uint256 _chainID = 1; - - UUPSProxy _proxyViewer; - KYCViewer _implkycViewer; - KYCViewerUpgraded _implKYCViewerUpgraded; - KYCViewer _kycViewer; - KYCViewerUpgraded _kycViewer2; - - function setUp() public { - vm.chainId(_chainID); - vm.startPrank(address(1)); - _owner.transfer(1e18); - vm.stopPrank(); - deployAAScaffolding(_owner, 1, _kycProvider, _recoverer); - vm.startPrank(_owner); - _implkycViewer = new KYCViewer{salt: 0}(address(_walletFactory)); - // deploy _proxy contract and point it to _implementation - _proxyViewer = new UUPSProxy{salt: 0}(address(_implkycViewer), ""); - // wrap in ABI to support easier calls - _kycViewer = KYCViewer(address(_proxyViewer)); - // Initialize kyc viewer _proxy - _kycViewer.initialize(); - vm.stopPrank(); - } - - function testUp() public { - console.log("address owner", address(_owner)); +contract KYCViewerTest is SharedSetup { + function testUp() public override { + super.testUp(); assertEq(_kycViewer.owner(), _owner); assertEq(address(_entryPoint.walletFactory()), address(_kycViewer.walletFactory())); - address kintoID = address(_kycViewer.kintoID()); - assertEq(address(_walletFactory.kintoID()), kintoID); + assertEq(address(_walletFactory.kintoID()), address(_kycViewer.kintoID())); } - /* ============ Upgrade Tests ============ */ + /* ============ Upgrade tests ============ */ - function testOwnerCanUpgradeViewer() public { - vm.startPrank(_owner); + function testUpgradeTo() public { KYCViewerUpgraded _implementationV2 = new KYCViewerUpgraded(address(_walletFactory)); + vm.prank(_owner); _kycViewer.upgradeTo(address(_implementationV2)); - // re-wrap the _proxy - _kycViewer2 = KYCViewerUpgraded(address(_kycViewer)); - assertEq(_kycViewer2.newFunction(), 1); - vm.stopPrank(); + assertEq(KYCViewerUpgraded(address(_kycViewer)).newFunction(), 1); } - function test_RevertWhen_OthersCannotUpgradeFactory() public { + function testUpgradeTo_RevertWhen_CallerIsNotOwner(address someone) public { + vm.assume(someone != _owner); KYCViewerUpgraded _implementationV2 = new KYCViewerUpgraded(address(_walletFactory)); vm.expectRevert("only owner"); + vm.prank(someone); _kycViewer.upgradeTo(address(_implementationV2)); } - /* ============ Viewer Tests ============ */ + /* ============ Viewer tests ============ */ function testIsKYCBothOwnerAndWallet() public { assertEq(_kycViewer.isKYC(address(_kintoWallet)), _kycViewer.isKYC(_owner)); assertEq(_kycViewer.isIndividual(address(_kintoWallet)), _kycViewer.isIndividual(_owner)); assertEq(_kycViewer.isCompany(address(_kintoWallet)), false); assertEq(_kycViewer.hasTrait(address(_kintoWallet), 6), false); + assertEq(_kycViewer.isSanctionsSafe(address(_kintoWallet)), true); + assertEq(_kycViewer.isSanctionsSafeIn(address(_kintoWallet), 1), true); } } diff --git a/test/KintoAppRegistry.t.sol b/test/KintoAppRegistry.t.sol index 002847216..f12bf5c8d 100644 --- a/test/KintoAppRegistry.t.sol +++ b/test/KintoAppRegistry.t.sol @@ -6,7 +6,7 @@ import "forge-std/console.sol"; import "../src/apps/KintoAppRegistry.sol"; -import "./KintoWallet.t.sol"; +import "./SharedSetup.t.sol"; contract KintoAppRegistryV2 is KintoAppRegistry { function newFunction() external pure returns (uint256) { @@ -16,9 +16,10 @@ contract KintoAppRegistryV2 is KintoAppRegistry { constructor(IKintoWalletFactory _walletFactory) KintoAppRegistry(_walletFactory) {} } -contract KintoAppRegistryTest is KintoWalletTest { +contract KintoAppRegistryTest is SharedSetup { function testUp() public override { super.testUp(); + useHarness(); assertEq(_kintoAppRegistry.owner(), _owner); assertEq(_kintoAppRegistry.name(), "Kinto APP"); @@ -27,11 +28,15 @@ contract KintoAppRegistryTest is KintoWalletTest { assertEq(_kintoAppRegistry.RATE_LIMIT_THRESHOLD(), 10); assertEq(_kintoAppRegistry.GAS_LIMIT_PERIOD(), 30 days); assertEq(_kintoAppRegistry.GAS_LIMIT_THRESHOLD(), 1e16); + assertEq( + KintoAppRegistryHarness(address(_kintoAppRegistry)).exposed_baseURI(), + "https://kinto.xyz/metadata/kintoapp/" + ); } - /* ============ Upgrade Tests ============ */ + /* ============ Upgrade tests ============ */ - function testOwnerCanUpgradeApp() public { + function testUpgradeTo() public { vm.startPrank(_owner); KintoAppRegistryV2 _implementationV2 = new KintoAppRegistryV2(_walletFactory); @@ -41,13 +46,13 @@ contract KintoAppRegistryTest is KintoWalletTest { vm.stopPrank(); } - function test_RevertWhen_OthersCannotUpgradeAppRegistry() public { + function testUpgradeTo_RevertWhen_CallerIsNotOwner() public { KintoAppRegistryV2 _implementationV2 = new KintoAppRegistryV2(_walletFactory); vm.expectRevert("Ownable: caller is not the owner"); _kintoAppRegistry.upgradeTo(address(_implementationV2)); } - /* ============ App Tests & Viewers ============ */ + /* ============ App tests & Viewers ============ */ function testRegisterApp(string memory name, address parentContract) public { address[] memory appContracts = new address[](1); @@ -102,7 +107,50 @@ contract KintoAppRegistryTest is KintoWalletTest { assertEq(metadata.name, name); } - function testUpdateApp(string memory name, address parentContract) public { + function testRegisterApp_RevertWhen_ChildrenIsWallet(string memory name, address parentContract) public { + uint256[4] memory appLimits = [uint256(0), uint256(0), uint256(0), uint256(0)]; + address[] memory appContracts = new address[](1); + appContracts[0] = address(_kintoWallet); + + // register app + vm.expectRevert("Wallets can not be registered"); + _kintoAppRegistry.registerApp(name, parentContract, appContracts, appLimits); + } + + function testRegisterApp_RevertWhen_AlreadyRegistered() public { + // register app + string memory name = "app"; + address parentContract = address(_engenCredits); + uint256[4] memory appLimits = [uint256(0), uint256(0), uint256(0), uint256(0)]; + address[] memory appContracts = new address[](0); + + vm.prank(_owner); + _kintoAppRegistry.registerApp(name, parentContract, appContracts, appLimits); + + // try to register again + vm.expectRevert("App already registered"); + _kintoAppRegistry.registerApp(name, parentContract, appContracts, appLimits); + } + + function testRegisterApp_RevertWhen_ParentIsChild() public { + // register app with child address 2 + string memory name = "app"; + address parentContract = address(_engenCredits); + uint256[4] memory appLimits = [uint256(0), uint256(0), uint256(0), uint256(0)]; + address[] memory appContracts = new address[](1); + appContracts[0] = address(2); + + vm.prank(_owner); + _kintoAppRegistry.registerApp(name, parentContract, appContracts, appLimits); + + // registering app "app2" with parent address 2 should revert + parentContract = address(2); + appContracts = new address[](0); + vm.expectRevert("Parent contract already registered as a child"); + _kintoAppRegistry.registerApp(name, parentContract, appContracts, appLimits); + } + + function testUpdateMetadata(string memory name, address parentContract) public { address[] memory appContracts = new address[](1); appContracts[0] = address(8); @@ -135,108 +183,96 @@ contract KintoAppRegistryTest is KintoWalletTest { assertEq(_kintoAppRegistry.isSponsored(parentContract, address(8)), true); } - function testRegisterApp_RevertWhen_ChildrenIsWallet(string memory name, address parentContract) public { - uint256[4] memory appLimits = [uint256(0), uint256(0), uint256(0), uint256(0)]; - address[] memory appContracts = new address[](1); - appContracts[0] = address(_kintoWallet); + function testUpdateMetadata_RevertWhen_CallerIsNotDeveloper(string memory name, address parentContract) public { + registerApp(_owner, name, parentContract); - // register app - vm.expectRevert("Wallets can not be registered"); - _kintoAppRegistry.registerApp(name, parentContract, appContracts, appLimits); + // update app + address[] memory appContracts = new address[](0); + vm.prank(_user); + vm.expectRevert("Only developer can update metadata"); + _kintoAppRegistry.updateMetadata( + "test2", parentContract, appContracts, [uint256(1), uint256(1), uint256(1), uint256(1)] + ); } /* ============ DSA Test ============ */ function testEnableDSA_WhenCallerIsOwner() public { - address parentContract = address(_engenCredits); - address[] memory appContracts = new address[](1); - appContracts[0] = address(8); - uint256[] memory appLimits = new uint256[](4); - appLimits[0] = _kintoAppRegistry.RATE_LIMIT_PERIOD(); - appLimits[1] = _kintoAppRegistry.RATE_LIMIT_THRESHOLD(); - appLimits[2] = _kintoAppRegistry.GAS_LIMIT_PERIOD(); - appLimits[3] = _kintoAppRegistry.GAS_LIMIT_THRESHOLD(); + registerApp(_owner, "app", address(_engenCredits)); vm.prank(_owner); - _kintoAppRegistry.registerApp( - "", parentContract, appContracts, [appLimits[0], appLimits[1], appLimits[2], appLimits[3]] - ); + _kintoAppRegistry.enableDSA(address(_engenCredits)); - vm.prank(_owner); - _kintoAppRegistry.enableDSA(parentContract); - - IKintoAppRegistry.Metadata memory metadata = _kintoAppRegistry.getAppMetadata(parentContract); + IKintoAppRegistry.Metadata memory metadata = _kintoAppRegistry.getAppMetadata(address(_engenCredits)); assertEq(metadata.dsaEnabled, true); } - function test_Revert_When_User_TriesToEnableDSA() public { - vm.startPrank(_user); - address parentContract = address(_engenCredits); - address[] memory appContracts = new address[](1); - appContracts[0] = address(8); - uint256[] memory appLimits = new uint256[](4); - appLimits[0] = _kintoAppRegistry.RATE_LIMIT_PERIOD(); - appLimits[1] = _kintoAppRegistry.RATE_LIMIT_THRESHOLD(); - appLimits[2] = _kintoAppRegistry.GAS_LIMIT_PERIOD(); - appLimits[3] = _kintoAppRegistry.GAS_LIMIT_THRESHOLD(); - _kintoAppRegistry.registerApp( - "", parentContract, appContracts, [appLimits[0], appLimits[1], appLimits[2], appLimits[3]] - ); + function testEnableDSA_RevertWhen_CallerIsNotOwner() public { + registerApp(_owner, "app", address(_engenCredits)); + vm.expectRevert("Ownable: caller is not the owner"); - _kintoAppRegistry.enableDSA(parentContract); + _kintoAppRegistry.enableDSA(address(_engenCredits)); + } + + function testEnableDSA_RevertWhen_AlreadyEnabled() public { + registerApp(_owner, "app", address(_engenCredits)); + vm.prank(_owner); + _kintoAppRegistry.enableDSA(address(_engenCredits)); + + vm.expectRevert("DSA already enabled"); + vm.prank(_owner); + _kintoAppRegistry.enableDSA(address(_engenCredits)); } /* ============ Sponsored Contracts Test ============ */ - function testAppCreatorCanSetSponsoredContracts() public { - vm.startPrank(_user); - address parentContract = address(_engenCredits); - address[] memory appContracts = new address[](1); - appContracts[0] = address(8); - uint256[] memory appLimits = new uint256[](4); - appLimits[0] = _kintoAppRegistry.RATE_LIMIT_PERIOD(); - appLimits[1] = _kintoAppRegistry.RATE_LIMIT_THRESHOLD(); - appLimits[2] = _kintoAppRegistry.GAS_LIMIT_PERIOD(); - appLimits[3] = _kintoAppRegistry.GAS_LIMIT_THRESHOLD(); - _kintoAppRegistry.registerApp( - "", parentContract, appContracts, [appLimits[0], appLimits[1], appLimits[2], appLimits[3]] - ); + function testSetSponsoredContracts() public { + registerApp(_owner, "app", address(_engenCredits)); + address[] memory contracts = new address[](2); contracts[0] = address(8); contracts[1] = address(9); + bool[] memory flags = new bool[](2); flags[0] = false; flags[1] = true; - _kintoAppRegistry.setSponsoredContracts(parentContract, contracts, flags); - assertEq(_kintoAppRegistry.isSponsored(parentContract, address(8)), true); // child contracts always sponsored - assertEq(_kintoAppRegistry.isSponsored(parentContract, address(9)), true); - assertEq(_kintoAppRegistry.isSponsored(parentContract, address(10)), false); - vm.stopPrank(); + + vm.prank(_owner); + _kintoAppRegistry.setSponsoredContracts(address(_engenCredits), contracts, flags); + + assertEq(_kintoAppRegistry.isSponsored(address(_engenCredits), address(8)), false); + assertEq(_kintoAppRegistry.isSponsored(address(_engenCredits), address(9)), true); + assertEq(_kintoAppRegistry.isSponsored(address(_engenCredits), address(10)), false); } - function test_Revert_When_NotCreator_TriesToSetSponsoredContracts() public { - vm.startPrank(_user); - address parentContract = address(_engenCredits); - address[] memory appContracts = new address[](1); - appContracts[0] = address(8); - uint256[] memory appLimits = new uint256[](4); - appLimits[0] = _kintoAppRegistry.RATE_LIMIT_PERIOD(); - appLimits[1] = _kintoAppRegistry.RATE_LIMIT_THRESHOLD(); - appLimits[2] = _kintoAppRegistry.GAS_LIMIT_PERIOD(); - appLimits[3] = _kintoAppRegistry.GAS_LIMIT_THRESHOLD(); - _kintoAppRegistry.registerApp( - "", parentContract, appContracts, [appLimits[0], appLimits[1], appLimits[2], appLimits[3]] - ); + function testSetSponsoredContracts_RevertWhen_CallerIsNotCreator() public { + registerApp(_owner, "app", address(_engenCredits)); + address[] memory contracts = new address[](2); contracts[0] = address(8); contracts[1] = address(9); + bool[] memory flags = new bool[](2); flags[0] = false; flags[1] = true; - vm.startPrank(_user2); + + vm.prank(_user); vm.expectRevert("Only developer can set sponsored contracts"); - _kintoAppRegistry.setSponsoredContracts(parentContract, contracts, flags); - vm.stopPrank(); + _kintoAppRegistry.setSponsoredContracts(address(_engenCredits), contracts, flags); + } + + function testSetSponsoredContracts_RevertWhen_LengthMismatch() public { + registerApp(_owner, "app", address(_engenCredits)); + + address[] memory contracts = new address[](1); + contracts[0] = address(8); + + bool[] memory flags = new bool[](2); + flags[0] = false; + flags[1] = true; + + vm.expectRevert("Invalid input"); + _kintoAppRegistry.setSponsoredContracts(address(_engenCredits), contracts, flags); } /* ============ Transfer Test ============ */ @@ -258,4 +294,17 @@ contract KintoAppRegistryTest is KintoWalletTest { vm.expectRevert("Only mint transfers are allowed"); _kintoAppRegistry.safeTransferFrom(_user, _user2, tokenIdx); } + + /* ============ Supports Interface tests ============ */ + + function testSupportsInterface() public { + bytes4 InterfaceERC721Upgradeable = bytes4(keccak256("balanceOf(address)")) + ^ bytes4(keccak256("ownerOf(uint256)")) ^ bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)")) + ^ bytes4(keccak256("safeTransferFrom(address,address,uint256)")) + ^ bytes4(keccak256("transferFrom(address,address,uint256)")) ^ bytes4(keccak256("approve(address,uint256)")) + ^ bytes4(keccak256("setApprovalForAll(address,bool)")) ^ bytes4(keccak256("getApproved(uint256)")) + ^ bytes4(keccak256("isApprovedForAll(address,address)")); + + assertTrue(_kintoID.supportsInterface(InterfaceERC721Upgradeable)); + } } diff --git a/test/KintoEntryPoint.t.sol b/test/KintoEntryPoint.t.sol index dd1c892c1..38530f7ba 100644 --- a/test/KintoEntryPoint.t.sol +++ b/test/KintoEntryPoint.t.sol @@ -4,14 +4,14 @@ pragma solidity ^0.8.18; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import "./KintoWallet.t.sol"; +import "./SharedSetup.t.sol"; -contract KintoEntryPointTest is KintoWalletTest { +contract KintoEntryPointTest is SharedSetup { function testUp() public override { assertEq(_entryPoint.walletFactory(), address(_walletFactory)); } - /* ============ Deployment Tests ============ */ + /* ============ Deployment tests ============ */ function testCannotResetWalletFactoryAddress() public { vm.startPrank(_owner); diff --git a/test/KintoID.t.sol b/test/KintoID.t.sol index ed26c0421..bf3a9b592 100644 --- a/test/KintoID.t.sol +++ b/test/KintoID.t.sol @@ -34,25 +34,25 @@ contract KintoIDTest is KYCSignature, AATestScaffolding, UserOp { _proxy = new UUPSProxy(address(_implementation), ""); // wrap in ABI to support easier calls - _kintoIDv1 = KintoID(address(_proxy)); + _kintoID = KintoID(address(_proxy)); // Initialize _proxy - _kintoIDv1.initialize(); - _kintoIDv1.grantRole(_kintoIDv1.KYC_PROVIDER_ROLE(), _kycProvider); + _kintoID.initialize(); + _kintoID.grantRole(_kintoID.KYC_PROVIDER_ROLE(), _kycProvider); vm.stopPrank(); } function testUp() public { - assertEq(_kintoIDv1.lastMonitoredAt(), block.timestamp); - assertEq(_kintoIDv1.name(), "Kinto ID"); - assertEq(_kintoIDv1.symbol(), "KINTOID"); + assertEq(_kintoID.lastMonitoredAt(), block.timestamp); + assertEq(_kintoID.name(), "Kinto ID"); + assertEq(_kintoID.symbol(), "KINTOID"); } /* ============ Upgrade tests ============ */ - function testOwnerCanUpgrade() public { + function testUpgradeTo() public { vm.startPrank(_owner); KintoIDv2 _implementationV2 = new KintoIDv2(); - _kintoIDv1.upgradeTo(address(_implementationV2)); + _kintoID.upgradeTo(address(_implementationV2)); // ensure that the _proxy is now pointing to the new implementation _kintoIDv2 = KintoIDv2(address(_proxy)); @@ -60,7 +60,7 @@ contract KintoIDTest is KYCSignature, AATestScaffolding, UserOp { vm.stopPrank(); } - function test_RevertWhen_OthersCannotUpgrade() public { + function testUpgradeTo_RevertWhen_CallerIsNotOwner() public { KintoIDv2 _implementationV2 = new KintoIDv2(); bytes memory err = abi.encodePacked( @@ -70,22 +70,22 @@ contract KintoIDTest is KYCSignature, AATestScaffolding, UserOp { Strings.toHexString(uint256(_implementationV2.UPGRADER_ROLE()), 32) ); vm.expectRevert(err); - _kintoIDv1.upgradeTo(address(_implementationV2)); + _kintoID.upgradeTo(address(_implementationV2)); } function testAuthorizedCanUpgrade() public { - assertEq(false, _kintoIDv1.hasRole(_kintoIDv1.UPGRADER_ROLE(), _upgrader)); + assertEq(false, _kintoID.hasRole(_kintoID.UPGRADER_ROLE(), _upgrader)); vm.startPrank(_owner); - _kintoIDv1.grantRole(_kintoIDv1.UPGRADER_ROLE(), _upgrader); + _kintoID.grantRole(_kintoID.UPGRADER_ROLE(), _upgrader); vm.stopPrank(); // upgrade from the _upgrader account - assertEq(true, _kintoIDv1.hasRole(_kintoIDv1.UPGRADER_ROLE(), _upgrader)); + assertEq(true, _kintoID.hasRole(_kintoID.UPGRADER_ROLE(), _upgrader)); KintoIDv2 _implementationV2 = new KintoIDv2(); vm.prank(_upgrader); - _kintoIDv1.upgradeTo(address(_implementationV2)); + _kintoID.upgradeTo(address(_implementationV2)); // re-wrap the _proxy _kintoIDv2 = KintoIDv2(address(_proxy)); @@ -95,166 +95,169 @@ contract KintoIDTest is KYCSignature, AATestScaffolding, UserOp { /* ============ Mint tests ============ */ function testMintIndividualKYC() public { - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](0); vm.startPrank(_kycProvider); - assertEq(_kintoIDv1.isKYC(_user), false); - _kintoIDv1.mintIndividualKyc(sigdata, traits); - assertEq(_kintoIDv1.isKYC(_user), true); - assertEq(_kintoIDv1.isIndividual(_user), true); - assertEq(_kintoIDv1.mintedAt(_user), block.timestamp); - assertEq(_kintoIDv1.hasTrait(_user, 1), false); - assertEq(_kintoIDv1.hasTrait(_user, 2), false); - assertEq(_kintoIDv1.balanceOf(_user), 1); + assertEq(_kintoID.isKYC(_user), false); + _kintoID.mintIndividualKyc(sigdata, traits); + assertEq(_kintoID.isKYC(_user), true); + assertEq(_kintoID.isIndividual(_user), true); + assertEq(_kintoID.mintedAt(_user), block.timestamp); + assertEq(_kintoID.hasTrait(_user, 1), false); + assertEq(_kintoID.hasTrait(_user, 2), false); + assertEq(_kintoID.balanceOf(_user), 1); } function testMintCompanyKYC() public { - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](2); traits[0] = 2; traits[1] = 5; vm.startPrank(_kycProvider); - _kintoIDv1.mintCompanyKyc(sigdata, traits); - assertEq(_kintoIDv1.isKYC(_user), true); - assertEq(_kintoIDv1.isCompany(_user), true); - assertEq(_kintoIDv1.mintedAt(_user), block.timestamp); - assertEq(_kintoIDv1.hasTrait(_user, 1), false); - assertEq(_kintoIDv1.hasTrait(_user, 2), true); - assertEq(_kintoIDv1.hasTrait(_user, 5), true); - assertEq(_kintoIDv1.balanceOf(_user), 1); - } - - function testMintIndividualKYCWithInvalidSender() public { - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + _kintoID.mintCompanyKyc(sigdata, traits); + assertEq(_kintoID.isKYC(_user), true); + assertEq(_kintoID.isCompany(_user), true); + assertEq(_kintoID.mintedAt(_user), block.timestamp); + assertEq(_kintoID.hasTrait(_user, 1), false); + assertEq(_kintoID.hasTrait(_user, 2), true); + assertEq(_kintoID.hasTrait(_user, 5), true); + assertEq(_kintoID.balanceOf(_user), 1); + } + + function testMintIndividualKYC_RevertWhen_InvalidSender() public { + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](1); traits[0] = 1; vm.startPrank(_user); vm.expectRevert("Invalid Provider"); - _kintoIDv1.mintIndividualKyc(sigdata, traits); + _kintoID.mintIndividualKyc(sigdata, traits); } - function testMintIndividualKYCWithInvalidSigner() public { - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 5, block.timestamp + 1000); + function testMintIndividualKYC_RevertWhen_InvalidSigner() public { + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, 5, block.timestamp + 1000); uint16[] memory traits = new uint16[](1); traits[0] = 1; vm.startPrank(_kycProvider); vm.expectRevert("Invalid Signer"); - _kintoIDv1.mintIndividualKyc(sigdata, traits); + _kintoID.mintIndividualKyc(sigdata, traits); } - function testMintIndividualKYCWithInvalidNonce() public { - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + function testMintIndividualKYC_RevertWhen_InvalidNonce() public { + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](1); traits[0] = 1; vm.startPrank(_kycProvider); - _kintoIDv1.mintIndividualKyc(sigdata, traits); + _kintoID.mintIndividualKyc(sigdata, traits); vm.expectRevert("Invalid Nonce"); - _kintoIDv1.mintIndividualKyc(sigdata, traits); + _kintoID.mintIndividualKyc(sigdata, traits); } - function testMintIndividualKYCWithExpiredSignature() public { - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp - 1000); + function testMintIndividualKYC_RevertWhen_ExpiredSignature() public { + vm.warp(block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp - 1000); + uint16[] memory traits = new uint16[](1); traits[0] = 1; - vm.startPrank(_kycProvider); + + vm.prank(_kycProvider); vm.expectRevert("Signature has expired"); - _kintoIDv1.mintIndividualKyc(sigdata, traits); + _kintoID.mintIndividualKyc(sigdata, traits); } function testMintIndividualKYC_RevertWhen_AlreadyMinted() public { - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](0); vm.prank(_kycProvider); - _kintoIDv1.mintIndividualKyc(sigdata, traits); + _kintoID.mintIndividualKyc(sigdata, traits); // try minting again should revert - sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); vm.expectRevert("Balance before mint must be 0"); vm.prank(_kycProvider); - _kintoIDv1.mintIndividualKyc(sigdata, traits); + _kintoID.mintIndividualKyc(sigdata, traits); } /* ============ Burn tests ============ */ function testBurnKYC_RevertWhen_UsingBurn() public { vm.expectRevert("Use burnKYC instead"); - _kintoIDv1.burn(1); + _kintoID.burn(1); } function test_RevertWhen_BurnIsCalled() public { approveKYC(_kycProvider, _user, _userPk); - uint256 tokenIdx = _kintoIDv1.tokenOfOwnerByIndex(_user, 0); + uint256 tokenIdx = _kintoID.tokenOfOwnerByIndex(_user, 0); vm.prank(_user); vm.expectRevert("Use burnKYC instead"); - _kintoIDv1.burn(tokenIdx); + _kintoID.burn(tokenIdx); } function testBurnKYC() public { - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](1); traits[0] = 1; vm.startPrank(_kycProvider); - _kintoIDv1.mintIndividualKyc(sigdata, traits); - assertEq(_kintoIDv1.isKYC(_user), true); - sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); - _kintoIDv1.burnKYC(sigdata); - assertEq(_kintoIDv1.balanceOf(_user), 0); + _kintoID.mintIndividualKyc(sigdata, traits); + assertEq(_kintoID.isKYC(_user), true); + sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); + _kintoID.burnKYC(sigdata); + assertEq(_kintoID.balanceOf(_user), 0); } function testOnlyProviderCanBurnKYC() public { - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](1); traits[0] = 1; vm.startPrank(_kycProvider); - _kintoIDv1.mintIndividualKyc(sigdata, traits); - assertEq(_kintoIDv1.isKYC(_user), true); - sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + _kintoID.mintIndividualKyc(sigdata, traits); + assertEq(_kintoID.isKYC(_user), true); + sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); vm.stopPrank(); vm.startPrank(_user); vm.expectRevert("Invalid Provider"); - _kintoIDv1.burnKYC(sigdata); + _kintoID.burnKYC(sigdata); } function testBurnFailsWithoutMinting() public { - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); vm.startPrank(_kycProvider); vm.expectRevert("Nothing to burn"); - _kintoIDv1.burnKYC(sigdata); + _kintoID.burnKYC(sigdata); } function testBurningTwiceFails() public { - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](1); traits[0] = 1; vm.startPrank(_kycProvider); - _kintoIDv1.mintIndividualKyc(sigdata, traits); - assertEq(_kintoIDv1.isKYC(_user), true); - sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); - _kintoIDv1.burnKYC(sigdata); - sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + _kintoID.mintIndividualKyc(sigdata, traits); + assertEq(_kintoID.isKYC(_user), true); + sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); + _kintoID.burnKYC(sigdata); + sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); vm.expectRevert("Nothing to burn"); - _kintoIDv1.burnKYC(sigdata); + _kintoID.burnKYC(sigdata); } /* ============ Monitor tests ============ */ function testMonitorNoChanges() public { vm.startPrank(_kycProvider); - _kintoIDv1.monitor(new address[](0), new IKintoID.MonitorUpdateData[][](0)); - assertEq(_kintoIDv1.lastMonitoredAt(), block.timestamp); + _kintoID.monitor(new address[](0), new IKintoID.MonitorUpdateData[][](0)); + assertEq(_kintoID.lastMonitoredAt(), block.timestamp); } function test_RevertWhen_LenghtMismatch() public { vm.expectRevert("Length mismatch"); vm.prank(_kycProvider); - _kintoIDv1.monitor(new address[](2), new IKintoID.MonitorUpdateData[][](1)); + _kintoID.monitor(new address[](2), new IKintoID.MonitorUpdateData[][](1)); } function test_RevertWhen_TooManyAccounts() public { vm.expectRevert("Too many accounts to monitor at once"); vm.prank(_kycProvider); - _kintoIDv1.monitor(new address[](201), new IKintoID.MonitorUpdateData[][](201)); + _kintoID.monitor(new address[](201), new IKintoID.MonitorUpdateData[][](201)); } function test_RevertWhen_CallerIsNotProvider(address someone) public { @@ -263,21 +266,21 @@ contract KintoIDTest is KYCSignature, AATestScaffolding, UserOp { "AccessControl: account ", Strings.toHexString(address(this)), " is missing role ", - Strings.toHexString(uint256(_kintoIDv1.KYC_PROVIDER_ROLE()), 32) + Strings.toHexString(uint256(_kintoID.KYC_PROVIDER_ROLE()), 32) ); vm.expectRevert(err); - _kintoIDv1.monitor(new address[](0), new IKintoID.MonitorUpdateData[][](0)); + _kintoID.monitor(new address[](0), new IKintoID.MonitorUpdateData[][](0)); } function testIsSanctionsMonitored() public { vm.prank(_kycProvider); - _kintoIDv1.monitor(new address[](0), new IKintoID.MonitorUpdateData[][](0)); - assertEq(_kintoIDv1.isSanctionsMonitored(1), true); + _kintoID.monitor(new address[](0), new IKintoID.MonitorUpdateData[][](0)); + assertEq(_kintoID.isSanctionsMonitored(1), true); vm.warp(block.timestamp + 7 days); - assertEq(_kintoIDv1.isSanctionsMonitored(8), true); - assertEq(_kintoIDv1.isSanctionsMonitored(6), false); + assertEq(_kintoID.isSanctionsMonitored(8), true); + assertEq(_kintoID.isSanctionsMonitored(6), false); } function testMonitor_WhenPassingTraitsAndSactions() public { @@ -295,23 +298,23 @@ contract KintoIDTest is KYCSignature, AATestScaffolding, UserOp { updates[0][3] = IKintoID.MonitorUpdateData(false, false, 2); // remove sanction 2 vm.prank(_kycProvider); - _kintoIDv1.monitor(accounts, updates); + _kintoID.monitor(accounts, updates); - assertEq(_kintoIDv1.hasTrait(_user, 5), true); - assertEq(_kintoIDv1.hasTrait(_user, 1), false); - assertEq(_kintoIDv1.isSanctionsSafeIn(_user, 5), true); - assertEq(_kintoIDv1.isSanctionsSafeIn(_user, 1), true); - assertEq(_kintoIDv1.isSanctionsSafeIn(_user, 6), false); - assertEq(_kintoIDv1.isSanctionsSafeIn(_user, 2), true); + assertEq(_kintoID.hasTrait(_user, 5), true); + assertEq(_kintoID.hasTrait(_user, 1), false); + assertEq(_kintoID.isSanctionsSafeIn(_user, 5), true); + assertEq(_kintoID.isSanctionsSafeIn(_user, 1), true); + assertEq(_kintoID.isSanctionsSafeIn(_user, 6), false); + assertEq(_kintoID.isSanctionsSafeIn(_user, 2), true); } - /* ============ Trait Tests ============ */ + /* ============ Trait tests ============ */ function testAddTrait() public { approveKYC(_kycProvider, _user, _userPk); vm.prank(_kycProvider); - _kintoIDv1.addTrait(_user, 1); - assertEq(_kintoIDv1.lastMonitoredAt(), block.timestamp); + _kintoID.addTrait(_user, 1); + assertEq(_kintoID.lastMonitoredAt(), block.timestamp); } function testAddTrait_RevertWhen_CallerIsNotProvider() public { @@ -320,31 +323,31 @@ contract KintoIDTest is KYCSignature, AATestScaffolding, UserOp { "AccessControl: account ", Strings.toHexString(_user), " is missing role ", - Strings.toHexString(uint256(_kintoIDv1.KYC_PROVIDER_ROLE()), 32) + Strings.toHexString(uint256(_kintoID.KYC_PROVIDER_ROLE()), 32) ); vm.expectRevert(err); vm.prank(_user); - _kintoIDv1.addTrait(_user, 1); + _kintoID.addTrait(_user, 1); } function testAddTrait_RevertWhen_UserIsNotKYCd() public { - assertEq(_kintoIDv1.isKYC(_user), false); + assertEq(_kintoID.isKYC(_user), false); vm.expectRevert("Account must have a KYC token"); vm.prank(_kycProvider); - _kintoIDv1.addTrait(_user, 1); + _kintoID.addTrait(_user, 1); } function testRemoveTrait() public { vm.startPrank(_kycProvider); - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](1); traits[0] = 1; - _kintoIDv1.mintIndividualKyc(sigdata, traits); - _kintoIDv1.addTrait(_user, 1); - assertEq(_kintoIDv1.hasTrait(_user, 1), true); - _kintoIDv1.removeTrait(_user, 1); - assertEq(_kintoIDv1.hasTrait(_user, 1), false); - assertEq(_kintoIDv1.lastMonitoredAt(), block.timestamp); + _kintoID.mintIndividualKyc(sigdata, traits); + _kintoID.addTrait(_user, 1); + assertEq(_kintoID.hasTrait(_user, 1), true); + _kintoID.removeTrait(_user, 1); + assertEq(_kintoID.hasTrait(_user, 1), false); + assertEq(_kintoID.lastMonitoredAt(), block.timestamp); } function testRemoveTrait_RevertWhen_CallerIsNotProvider() public { @@ -354,17 +357,17 @@ contract KintoIDTest is KYCSignature, AATestScaffolding, UserOp { "AccessControl: account ", Strings.toHexString(_user), " is missing role ", - Strings.toHexString(uint256(_kintoIDv1.KYC_PROVIDER_ROLE()), 32) + Strings.toHexString(uint256(_kintoID.KYC_PROVIDER_ROLE()), 32) ); vm.expectRevert(err); vm.prank(_user); - _kintoIDv1.removeTrait(_user, 1); + _kintoID.removeTrait(_user, 1); } function testRemoveTrait_RevertWhen_AccountIsNotKYCd() public { vm.expectRevert("Account must have a KYC token"); vm.prank(_kycProvider); - _kintoIDv1.removeTrait(_user, 1); + _kintoID.removeTrait(_user, 1); } function testTrais() public { @@ -372,45 +375,45 @@ contract KintoIDTest is KYCSignature, AATestScaffolding, UserOp { vm.startPrank(_kycProvider); - _kintoIDv1.addTrait(_user, 0); - _kintoIDv1.addTrait(_user, 1); - _kintoIDv1.addTrait(_user, 2); + _kintoID.addTrait(_user, 0); + _kintoID.addTrait(_user, 1); + _kintoID.addTrait(_user, 2); vm.stopPrank(); - bool[] memory traits = _kintoIDv1.traits(_user); + bool[] memory traits = _kintoID.traits(_user); assertEq(traits[0], true); assertEq(traits[1], true); assertEq(traits[2], true); assertEq(traits[3], false); } - /* ============ Sanction Tests ============ */ + /* ============ Sanction tests ============ */ function testAddSanction() public { vm.startPrank(_kycProvider); - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](1); traits[0] = 1; - _kintoIDv1.mintIndividualKyc(sigdata, traits); - _kintoIDv1.addSanction(_user, 1); - assertEq(_kintoIDv1.isSanctionsSafeIn(_user, 1), false); - assertEq(_kintoIDv1.isSanctionsSafe(_user), false); - assertEq(_kintoIDv1.lastMonitoredAt(), block.timestamp); + _kintoID.mintIndividualKyc(sigdata, traits); + _kintoID.addSanction(_user, 1); + assertEq(_kintoID.isSanctionsSafeIn(_user, 1), false); + assertEq(_kintoID.isSanctionsSafe(_user), false); + assertEq(_kintoID.lastMonitoredAt(), block.timestamp); } function testRemoveSancion() public { vm.startPrank(_kycProvider); - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](1); traits[0] = 1; - _kintoIDv1.mintIndividualKyc(sigdata, traits); - _kintoIDv1.addSanction(_user, 1); - assertEq(_kintoIDv1.isSanctionsSafeIn(_user, 1), false); - _kintoIDv1.removeSanction(_user, 1); - assertEq(_kintoIDv1.isSanctionsSafeIn(_user, 1), true); - assertEq(_kintoIDv1.isSanctionsSafe(_user), true); - assertEq(_kintoIDv1.lastMonitoredAt(), block.timestamp); + _kintoID.mintIndividualKyc(sigdata, traits); + _kintoID.addSanction(_user, 1); + assertEq(_kintoID.isSanctionsSafeIn(_user, 1), false); + _kintoID.removeSanction(_user, 1); + assertEq(_kintoID.isSanctionsSafeIn(_user, 1), true); + assertEq(_kintoID.isSanctionsSafe(_user), true); + assertEq(_kintoID.lastMonitoredAt(), block.timestamp); } function testAddSanction_RevertWhen_CallerIsNotKYCProvider() public { @@ -420,51 +423,51 @@ contract KintoIDTest is KYCSignature, AATestScaffolding, UserOp { "AccessControl: account ", Strings.toHexString(_user), " is missing role ", - Strings.toHexString(uint256(_kintoIDv1.KYC_PROVIDER_ROLE()), 32) + Strings.toHexString(uint256(_kintoID.KYC_PROVIDER_ROLE()), 32) ); vm.expectRevert(err); vm.prank(_user); - _kintoIDv1.addSanction(_user2, 1); + _kintoID.addSanction(_user2, 1); } function testRemoveSanction_RevertWhen_CallerIsNotKYCProvider() public { approveKYC(_kycProvider, _user, _userPk); vm.prank(_kycProvider); - _kintoIDv1.addSanction(_user, 1); - assertEq(_kintoIDv1.isSanctionsSafeIn(_user, 1), false); + _kintoID.addSanction(_user, 1); + assertEq(_kintoID.isSanctionsSafeIn(_user, 1), false); bytes memory err = abi.encodePacked( "AccessControl: account ", Strings.toHexString(_user), " is missing role ", - Strings.toHexString(uint256(_kintoIDv1.KYC_PROVIDER_ROLE()), 32) + Strings.toHexString(uint256(_kintoID.KYC_PROVIDER_ROLE()), 32) ); vm.expectRevert(err); vm.prank(_user); - _kintoIDv1.removeSanction(_user2, 1); + _kintoID.removeSanction(_user2, 1); } function testAddSanction_RevertWhen_AccountIsNotKYCd() public { vm.expectRevert("Account must have a KYC token"); vm.prank(_kycProvider); - _kintoIDv1.addSanction(_user, 1); + _kintoID.addSanction(_user, 1); } function testRemoveSanction_RevertWhen_AccountIsNotKYCd() public { vm.expectRevert("Account must have a KYC token"); vm.prank(_kycProvider); - _kintoIDv1.removeSanction(_user, 1); + _kintoID.removeSanction(_user, 1); } - /* ============ Transfer Tests ============ */ + /* ============ Transfer tests ============ */ function test_RevertWhen_TransfersAreDisabled() public { approveKYC(_kycProvider, _user, _userPk); - uint256 tokenIdx = _kintoIDv1.tokenOfOwnerByIndex(_user, 0); + uint256 tokenIdx = _kintoID.tokenOfOwnerByIndex(_user, 0); vm.prank(_user); vm.expectRevert("Only mint or burn transfers are allowed"); - _kintoIDv1.safeTransferFrom(_user, _user2, tokenIdx); + _kintoID.safeTransferFrom(_user, _user2, tokenIdx); } function testDappSignature() public { @@ -492,6 +495,6 @@ contract KintoIDTest is KYCSignature, AATestScaffolding, UserOp { ^ bytes4(keccak256("setApprovalForAll(address,bool)")) ^ bytes4(keccak256("getApproved(uint256)")) ^ bytes4(keccak256("isApprovedForAll(address,address)")); - assertTrue(_kintoIDv1.supportsInterface(InterfaceERC721Upgradeable)); + assertTrue(_kintoID.supportsInterface(InterfaceERC721Upgradeable)); } } diff --git a/test/KintoWalletFactory.t.sol b/test/KintoWalletFactory.t.sol index 69966ca9b..1d270b6c7 100644 --- a/test/KintoWalletFactory.t.sol +++ b/test/KintoWalletFactory.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.18; import "@aa/interfaces/IEntryPoint.sol"; import "@aa/core/EntryPoint.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -13,7 +14,7 @@ import "../src/sample/Counter.sol"; import "../src/interfaces/IKintoWallet.sol"; import "../src/wallet/KintoWallet.sol"; -import "./KintoWallet.t.sol"; +import "./SharedSetup.t.sol"; contract KintoWalletUpgrade is KintoWallet { constructor(IEntryPoint _entryPoint, IKintoID _kintoID, IKintoAppRegistry _kintoAppRegistry) @@ -33,7 +34,7 @@ contract KintoWalletFactoryUpgrade is KintoWalletFactory { } } -contract KintoWalletFactoryTest is KintoWalletTest { +contract KintoWalletFactoryTest is SharedSetup { using ECDSAUpgradeable for bytes32; using SignatureChecker for address; @@ -46,30 +47,69 @@ contract KintoWalletFactoryTest is KintoWalletTest { assertEq(_entryPoint.walletFactory(), address(_walletFactory)); } - /* ============ Upgrade Tests ============ */ + /* ============ Create Account tests ============ */ - function testOwnerCanUpgradeFactory() public { - vm.startPrank(_owner); + function testCreateAccount() public { + vm.prank(address(_owner)); + _kintoWallet = _walletFactory.createAccount(_owner, _owner, 0); + assertEq(_kintoWallet.owners(0), _owner); + } + + function testCreateAccount_WhenAlreadyExists() public { + vm.prank(address(_owner)); + _kintoWallet = _walletFactory.createAccount(_owner, _owner, 0); + + vm.prank(address(_owner)); + IKintoWallet _kintoWalletAfter = _walletFactory.createAccount(_owner, _owner, 0); + assertEq(address(_kintoWallet), address(_kintoWalletAfter)); + } + + function testCreateAccount_RevertWhen_ZeroAddress() public { + vm.prank(address(_owner)); + vm.expectRevert("invalid addresses"); + _kintoWallet = _walletFactory.createAccount(address(0), _owner, 0); + + vm.prank(address(_owner)); + vm.expectRevert("invalid addresses"); + _kintoWallet = _walletFactory.createAccount(_owner, address(0), 0); + } + + function testCreateAccount_RevertWhen_OwnerNotKYCd() public { + vm.prank(address(_user2)); + vm.expectRevert("KYC required"); + _kintoWallet = _walletFactory.createAccount(_user2, _owner, 0); + } + + function testCreateAccount_RevertWhen_OwnerAndSenderMismatch() public { + vm.prank(address(_owner)); + vm.expectRevert("KYC required"); + _kintoWallet = _walletFactory.createAccount(_user2, _owner, 0); + } + + /* ============ Upgrade tests ============ */ + + function testUpgradeTo() public { KintoWalletFactoryUpgrade _newImplementation = new KintoWalletFactoryUpgrade(_kintoWalletImpl); + vm.prank(_owner); _walletFactory.upgradeTo(address(_newImplementation)); - // re-wrap the _proxy - _walletFactoryv2 = KintoWalletFactoryUpgrade(address(_walletFactory)); - assertEq(_walletFactoryv2.newFunction(), 1); - vm.stopPrank(); + assertEq(KintoWalletFactoryUpgrade(address(_walletFactory)).newFunction(), 1); } - function test_RevertWhen_OthersCannotUpgradeFactory() public { + function testUpgradeTo_RevertWhen_CallerIsNotOwner(address someone) public { + vm.assume(someone != _owner); KintoWalletFactoryUpgrade _newImplementation = new KintoWalletFactoryUpgrade(_kintoWalletImpl); + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(someone); _walletFactory.upgradeTo(address(_newImplementation)); } - function testAllWalletsUpgrade() public { + function testUpgradeAllWalletImplementations() public { vm.startPrank(_owner); // Deploy a new wallet implementation _kintoWalletImpl = - KintoWallet(payable(address(new KintoWalletUpgrade(_entryPoint, _kintoIDv1, _kintoAppRegistry)))); + KintoWallet(payable(address(new KintoWalletUpgrade(_entryPoint, _kintoID, _kintoAppRegistry)))); // deploy walletv1 through wallet factory and initializes it _kintoWallet = _walletFactory.createAccount(_owner, _owner, 0); @@ -82,9 +122,9 @@ contract KintoWalletFactoryTest is KintoWalletTest { vm.stopPrank(); } - function testUpgrade_RevertWhen_CallerIsNotOwner() public { + function testUpgradeAllWalletImplementations_RevertWhen_CallerIsNotOwner() public { // deploy a new wallet implementation - _kintoWalletImpl = new KintoWalletUpgrade(_entryPoint, _kintoIDv1, _kintoAppRegistry); + _kintoWalletImpl = new KintoWalletUpgrade(_entryPoint, _kintoID, _kintoAppRegistry); // deploy walletv1 through wallet factory and initializes it vm.broadcast(_owner); @@ -95,95 +135,176 @@ contract KintoWalletFactoryTest is KintoWalletTest { _walletFactory.upgradeAllWalletImplementations(_kintoWalletImpl); } - /* ============ Deploy Tests ============ */ + function testUpgradeAllWalletImplementations_RevertWhen_ZeroAddress() public { + vm.prank(_owner); + vm.expectRevert("invalid address"); + _walletFactory.upgradeAllWalletImplementations(IKintoWallet(address(0))); + } - function testDeployCustomContract() public { - vm.startPrank(_owner); + function testUpgradeAllWalletImplementations_RevertWhen_BeaconAddress() public { + IKintoWallet _newImpl = IKintoWallet(UpgradeableBeacon(_walletFactory.beacon()).implementation()); + vm.prank(_owner); + vm.expectRevert("invalid address"); + _walletFactory.upgradeAllWalletImplementations(_newImpl); + } + + /* ============ Deploy tests ============ */ + + function testDeployContract() public { address computed = _walletFactory.getContractAddress(bytes32(0), keccak256(abi.encodePacked(type(Counter).creationCode))); + + vm.prank(_owner); address created = _walletFactory.deployContract(_owner, 0, abi.encodePacked(type(Counter).creationCode), bytes32(0)); + assertEq(computed, created); assertEq(Counter(created).count(), 0); + Counter(created).increment(); assertEq(Counter(created).count(), 1); - vm.stopPrank(); } - function testDeploy_RevertWhen_CreateWalletThroughDeploy() public { - vm.startPrank(_owner); + function testDeployContract_RevertWhen_CreateWalletThroughDeploy() public { bytes memory initialize = abi.encodeWithSelector(IKintoWallet.initialize.selector, _owner, _owner); bytes memory bytecode = abi.encodePacked( type(SafeBeaconProxy).creationCode, abi.encode(address(_walletFactory.beacon()), initialize) ); vm.expectRevert("Direct KintoWallet deployment not allowed"); + vm.prank(_owner); _walletFactory.deployContract(_owner, 0, bytecode, bytes32(0)); - vm.stopPrank(); } - function testSignerCanFundWallet() public { - vm.startPrank(_owner); + function testDeployContract_RevertWhen_SenderNotKYCd() public { + vm.prank(_user2); + vm.expectRevert("KYC required"); + _walletFactory.deployContract(_owner, 0, abi.encodePacked(type(Counter).creationCode), bytes32(0)); + } + + function testFundWallet() public { + vm.prank(_owner); _walletFactory.fundWallet{value: 1e18}(payable(address(_kintoWallet))); assertEq(address(_kintoWallet).balance, 1e18); } - function testWhitelistedSignerCanFundWallet() public { - vm.startPrank(_owner); - fundSponsorForApp(address(_kintoWallet)); - uint256 nonce = _kintoWallet.getNonce(); + function testFundWallet_WhenCallerIsWhitelisted() public { address[] memory funders = new address[](1); funders[0] = _funder; + bool[] memory flags = new bool[](1); flags[0] = true; - uint256[] memory privateKeys = new uint256[](1); - privateKeys[0] = 1; - UserOperation memory userOp = _createUserOperation( + + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( address(_kintoWallet), address(_kintoWallet), - nonce, + _kintoWallet.getNonce(), privateKeys, abi.encodeWithSignature("setFunderWhitelist(address[],bool[])", funders, flags), address(_paymaster) ); - UserOperation[] memory userOps = new UserOperation[](1); - userOps[0] = userOp; - // Execute the transaction via the entry point _entryPoint.handleOps(userOps, payable(_owner)); + vm.deal(_funder, 1e17); - vm.startPrank(_funder); + vm.prank(_funder); _walletFactory.fundWallet{value: 1e17}(payable(address(_kintoWallet))); assertEq(address(_kintoWallet).balance, 1e17); } - function testSignerCannotFundInvalidWallet() public { - vm.startPrank(_owner); + function testFundWallet_RevertWhen_InvalidWallet() public { vm.expectRevert("Invalid wallet or funder"); + vm.prank(_owner); _walletFactory.fundWallet{value: 1e18}(payable(address(0))); } - function testRandomSignerCannotFundWallet() public { - vm.deal(_user, 1e18); - vm.startPrank(_user); + function testFundWallet_RevertWhen_CallerIsInvalid() public { + vm.deal(_user, 1 ether); vm.expectRevert("Invalid wallet or funder"); - _walletFactory.fundWallet{value: 1e18}(payable(address(_kintoWallet))); + vm.prank(_user); + _walletFactory.fundWallet{value: 1 ether}(payable(address(_kintoWallet))); } - function testSignerCannotFundWalletWithoutEth() public { - vm.startPrank(_owner); + function testFundWallet_RevertWhen_NotEnoughETH() public { vm.expectRevert("Invalid wallet or funder"); + vm.prank(_owner); _walletFactory.fundWallet{value: 0}(payable(address(_kintoWallet))); } - /* ============ Recovery Tests ============ */ + /* ============ Recovery tests ============ */ + + function testStartWalletRecovery_WhenCallerIsRecoverer() public { + vm.prank(address(_kintoWallet.recoverer())); + _walletFactory.startWalletRecovery(payable(address(_kintoWallet))); + } + + function testStartWalletRecovery_WhenCallerIsRecoverer_RevertWhen_WalletNotExists() public { + vm.prank(address(_kintoWallet.recoverer())); + vm.expectRevert("invalid wallet"); + _walletFactory.startWalletRecovery(payable(address(123))); + } - function testStart_RevertWhen_RecoverNotRecoverer(address someone) public { - vm.assume(someone != address(_walletFactory)); + function testStartWalletRecovery_RevertWhen_CallerIsNotRecoverer(address someone) public { + vm.assume(someone != address(_kintoWallet.recoverer())); vm.prank(someone); vm.expectRevert("only recoverer"); _walletFactory.startWalletRecovery(payable(address(_kintoWallet))); } - /* ============ Send Money Tests ============ */ + function testCompleteWalletRecovery_WhenCallerIsRecoverer() public { + vm.prank(address(_kintoWallet.recoverer())); + _walletFactory.startWalletRecovery(payable(address(_kintoWallet))); + + vm.warp(block.timestamp + _kintoWallet.RECOVERY_TIME() + 1); + + // approve KYC for _user burn KYC for _owner + revokeKYC(_kycProvider, _owner, _ownerPk); + approveKYC(_kycProvider, _user, _userPk); + + // run monitor + address[] memory users = new address[](1); + users[0] = _user; + IKintoID.MonitorUpdateData[][] memory updates = new IKintoID.MonitorUpdateData[][](1); + updates[0] = new IKintoID.MonitorUpdateData[](1); + updates[0][0] = IKintoID.MonitorUpdateData(true, true, 5); + vm.prank(_kycProvider); + _kintoID.monitor(users, updates); + + vm.prank(address(_kintoWallet.recoverer())); + _walletFactory.completeWalletRecovery(payable(address(_kintoWallet)), users); + } + + function testCompleteWalletRecovery_RevertWhen_WhenCallerIsRecoverer_WalletNotExists() public { + vm.prank(address(_kintoWallet.recoverer())); + vm.expectRevert("invalid wallet"); + _walletFactory.completeWalletRecovery(payable(address(123)), new address[](0)); + } + + function testCompleteWalletRecovery_RevertWhen_CallerIsNotRecoverer(address someone) public { + vm.assume(someone != address(_kintoWallet.recoverer())); + vm.prank(someone); + vm.expectRevert("only recoverer"); + _walletFactory.completeWalletRecovery(payable(address(_kintoWallet)), new address[](0)); + } + + function testChangeWalletRecoverer_WhenCallerIsRecoverer() public { + vm.prank(address(_kintoWallet.recoverer())); + _walletFactory.changeWalletRecoverer(payable(address(_kintoWallet)), payable(address(123))); + } + + function testChangeWalletRecoverer_RevertWhen_CallerIsRecoverer_WhenWalletNotExists() public { + vm.prank(address(_kintoWallet.recoverer())); + vm.expectRevert("invalid wallet"); + _walletFactory.changeWalletRecoverer(payable(address(123)), payable(address(123))); + } + + function testChangeWalletRecoverer_RevertWhen_CallerIsNotRecoverer(address someone) public { + vm.assume(someone != address(_kintoWallet.recoverer())); + vm.prank(someone); + vm.expectRevert("only recoverer"); + _walletFactory.changeWalletRecoverer(payable(address(_kintoWallet)), payable(address(123))); + } + + /* ============ Send Money tests ============ */ function testSendMoneyToAccount_WhenCallerIsKYCd() public { approveKYC(_kycProvider, _user, _userPk); @@ -225,4 +346,35 @@ contract KintoWalletFactoryTest is KintoWalletTest { vm.expectRevert("KYC or Provider role required"); _walletFactory.sendMoneyToAccount{value: 1e18}(address(123)); } + + /* ============ Claim From Faucet tests ============ */ + + function testClaimFromFaucet_WhenCallerIsKYCd() public { + vm.prank(_owner); + _faucet.startFaucet{value: 1 ether}(); + + IFaucet.SignatureData memory sigdata = _auxCreateSignature(_faucet, _user, _userPk, block.timestamp + 1000); + vm.prank(_kycProvider); + _walletFactory.claimFromFaucet(address(_faucet), sigdata); + assertEq(_user.balance, _faucet.CLAIM_AMOUNT()); + } + + function testClaimFromFaucet_RevertWhen_CallerIsNotKYCd() public { + vm.prank(_owner); + _faucet.startFaucet{value: 1 ether}(); + + IFaucet.SignatureData memory sigdata = _auxCreateSignature(_faucet, _user, _userPk, block.timestamp + 1000); + vm.expectRevert("Invalid sender"); + _walletFactory.claimFromFaucet(address(_faucet), sigdata); + } + + function testClaimFromFaucet_RevertWhen_FaucetIsZeroAddress() public { + vm.prank(_owner); + _faucet.startFaucet{value: 1 ether}(); + + IFaucet.SignatureData memory sigdata = _auxCreateSignature(_faucet, _user, _userPk, block.timestamp + 1000); + vm.expectRevert("Invalid faucet address"); + vm.prank(_kycProvider); + _walletFactory.claimFromFaucet(address(0), sigdata); + } } diff --git a/test/KintoWallet.t.sol b/test/SharedSetup.t.sol similarity index 89% rename from test/KintoWallet.t.sol rename to test/SharedSetup.t.sol index 302e6cf0f..93dd6974c 100644 --- a/test/KintoWallet.t.sol +++ b/test/SharedSetup.t.sol @@ -13,10 +13,11 @@ import "../src/sample/Counter.sol"; import "./harness/KintoWalletHarness.sol"; import "./harness/SponsorPaymasterHarness.sol"; +import "./harness/KintoAppRegistryHarness.sol"; import {UserOp} from "./helpers/UserOp.sol"; import {AATestScaffolding} from "./helpers/AATestScaffolding.sol"; -contract KintoWalletTest is UserOp, AATestScaffolding { +contract SharedSetup is UserOp, AATestScaffolding { uint256[] privateKeys; Counter counter; @@ -27,6 +28,7 @@ contract KintoWalletTest is UserOp, AATestScaffolding { event KintoWalletInitialized(IEntryPoint indexed entryPoint, address indexed owner); event WalletPolicyChanged(uint256 newPolicy, uint256 oldPolicy); event RecovererChanged(address indexed newRecoverer, address indexed recoverer); + event PostOpRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason); function setUp() public virtual { deployAAScaffolding(_owner, 1, _kycProvider, _recoverer); diff --git a/test/SponsorPaymastExploit.t.sol b/test/SponsorPaymastExploit.t.sol index 9390e05bd..b73763c89 100644 --- a/test/SponsorPaymastExploit.t.sol +++ b/test/SponsorPaymastExploit.t.sol @@ -13,89 +13,68 @@ import "../src/paymasters/SponsorPaymaster.sol"; import "../src/KintoID.sol"; import "../src/sample/Counter.sol"; -import "./helpers/UserOp.sol"; -import "./helpers/KYCSignature.sol"; -import {AATestScaffolding} from "./helpers/AATestScaffolding.sol"; +import "./SharedSetup.t.sol"; -contract MyOpCreator is UserOp, KYCSignature { - function _createOp( - uint256 _chainID, - address _account, - uint256 nonce, - uint256[] calldata _privateKeyOwners, - address _target, - uint256 value, - bytes calldata _bytesOp, - address _paymaster - ) public view returns (UserOperation memory op) { - op = UserOperation({ - sender: _account, - nonce: nonce, - initCode: bytes(""), - callData: abi.encodeCall(KintoWallet.execute, (_target, value, _bytesOp)), - callGasLimit: 40000, // generate from call simulation - verificationGasLimit: 150000, // verification gas. will add create2 cost (3200+200*length) if initCode exists - preVerificationGas: 99e18, // should also cover calldata cost. - maxFeePerGas: 1, // grab from current gas - maxPriorityFeePerGas: 1e9, // grab from current gas - paymasterAndData: abi.encodePacked(_paymaster), - signature: bytes("") - }); - op.signature = _signUserOp(op, KintoWallet(payable(_account)).entryPoint(), _chainID, _privateKeyOwners); - return op; - } -} +// contract MyOpCreator is UserOp, KYCSignature { +// function _createOp( +// uint256 _chainID, +// address _account, +// uint256 nonce, +// uint256[] calldata _privateKeyOwners, +// address _target, +// uint256 value, +// bytes calldata _bytesOp, +// address _paymaster +// ) public view returns (UserOperation memory op) { +// op = UserOperation({ +// sender: _account, +// nonce: nonce, +// initCode: bytes(""), +// callData: abi.encodeCall(KintoWallet.execute, (_target, value, _bytesOp)), +// callGasLimit: 40000, // generate from call simulation +// verificationGasLimit: 150000, // verification gas. will add create2 cost (3200+200*length) if initCode exists +// preVerificationGas: 99e18, // should also cover calldata cost. +// maxFeePerGas: 1, // grab from current gas +// maxPriorityFeePerGas: 1e9, // grab from current gas +// paymasterAndData: abi.encodePacked(_paymaster), +// signature: bytes("") +// }); +// op.signature = _signUserOp(op, KintoWallet(payable(_account)).entryPoint(), _chainID, _privateKeyOwners); +// return op; +// } +// } -contract SponsorPaymasterExploitTest is MyOpCreator, AATestScaffolding { +contract SponsorPaymasterExploitTest is SharedSetup { using ECDSAUpgradeable for bytes32; using SignatureChecker for address; - uint256 _chainID = 1; - - function setUp() public { - vm.chainId(_chainID); - vm.startPrank(address(1)); - _owner.transfer(1e18); - vm.stopPrank(); - deployAAScaffolding(_owner, 1, _kycProvider, _recoverer); - fundSponsorForApp(address(_engenCredits)); - fundSponsorForApp(address(_kintoWallet)); - } - function testExploit() public { - vm.startPrank(_owner); - // Let's deploy the counter contract - Counter counter = new Counter(); - assertEq(counter.count(), 0); - vm.stopPrank(); - fundSponsorForApp(address(counter)); - vm.startPrank(_owner); - // Let's send a transaction to the counter contract through our wallet - uint256 nonce = _kintoWallet.getNonce(); - uint256[] memory privateKeys = new uint256[](1); - privateKeys[0] = 1; - - UserOperation memory userOp = this._createOp( - _chainID, + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( address(_kintoWallet), - nonce, - privateKeys, address(counter), - 0, + _kintoWallet.getNonce(), + privateKeys, abi.encodeWithSignature("increment()"), address(_paymaster) ); - UserOperation[] memory userOps = new UserOperation[](1); - userOps[0] = userOp; - // Execute the transaction via the entry point + userOps[0].callGasLimit = 40000; // generate from call simulation + userOps[0].verificationGasLimit = 150000; // verification gas. will add create2 cost (3200+200*length) if initCode exists + userOps[0].preVerificationGas = 99e18; // should also cover calldata cost. + userOps[0].maxFeePerGas = 1; // grab from current gas + userOps[0].maxPriorityFeePerGas = 1e9; // grab from current gas + userOps[0].signature = _signUserOp( + userOps[0], KintoWallet(payable(address(_kintoWallet))).entryPoint(), block.chainid, privateKeys + ); + uint256 balanceBefore = _owner.balance; console.log("HACKER BALANCE BEFORE", balanceBefore); + vm.expectRevert(); _entryPoint.handleOps(userOps, payable(_owner)); + uint256 balanceAfter = _owner.balance; uint256 dust = 100_000; assertGt(balanceBefore, balanceAfter - dust, "Hacker's profit is too HIGH"); - - vm.stopPrank(); } } diff --git a/test/SponsorPaymaster.t.sol b/test/SponsorPaymaster.t.sol index b4733b413..1d8e508c5 100644 --- a/test/SponsorPaymaster.t.sol +++ b/test/SponsorPaymaster.t.sol @@ -12,7 +12,7 @@ import "../src/paymasters/SponsorPaymaster.sol"; import "../src/sample/Counter.sol"; import "../src/interfaces/IKintoWallet.sol"; -import "./KintoWallet.t.sol"; +import "./SharedSetup.t.sol"; contract SponsorPaymasterUpgrade is SponsorPaymaster { constructor(IEntryPoint __entryPoint, address _owner) SponsorPaymaster(__entryPoint) { @@ -25,7 +25,7 @@ contract SponsorPaymasterUpgrade is SponsorPaymaster { } } -contract SponsorPaymasterTest is KintoWalletTest { +contract SponsorPaymasterTest is SharedSetup { function setUp() public override { super.setUp(); vm.deal(_user, 1e20); @@ -38,7 +38,7 @@ contract SponsorPaymasterTest is KintoWalletTest { /* ============ Upgrade ============ */ - function testOwnerCanUpgrade() public { + function testUpgradeTo() public { SponsorPaymasterUpgrade _newImplementation = new SponsorPaymasterUpgrade(_entryPoint, _owner); vm.prank(_owner); @@ -131,6 +131,24 @@ contract SponsorPaymasterTest is KintoWalletTest { vm.stopPrank(); } + function testDepositInfo_WhenCallerDepositsToSomeoneElse(address someone) public { + vm.assume(someone != _owner); + vm.prank(_owner); + _paymaster.addDepositFor{value: 5e18}(address(someone)); + + (uint256 amount, uint256 _unlockBlock) = _paymaster.depositInfo(address(someone)); + assertEq(amount, 5e18); + assertEq(_unlockBlock, 0); + } + + function testDepositInfo_WhenCallerDepositsToHimself() public { + vm.prank(_owner); + _paymaster.addDepositFor{value: 5e18}(address(_owner)); + (uint256 amount, uint256 _unlockBlock) = _paymaster.depositInfo(address(_owner)); + assertEq(amount, 5e18); + assertEq(_unlockBlock, 0); + } + /* ============ Per-Op: Global Rate limits ============ */ function testValidatePaymasterUserOp() public { @@ -240,6 +258,39 @@ contract SponsorPaymasterTest is KintoWalletTest { /* ============ Global Rate limits (tx & batched ops rates) ============ */ + function testAppLimits() public { + (uint256 operationCount, uint256 lastOperationTime, uint256 ethCostCount, uint256 costLimitLastOperationTime) = + _paymaster.appUserLimit(address(_kintoWallet), address(counter)); + assertTrue(operationCount == 0); + assertTrue(lastOperationTime == 0); + assertTrue(ethCostCount == 0); + assertTrue(costLimitLastOperationTime == 0); + + uint256[4] memory appLimits = _kintoAppRegistry.getContractLimits(address(counter)); + _incrementCounterTxs(appLimits[1] - 1, address(counter)); + + // move time to GAS_LIMIT_PERIOD + 1 and call monitor so we keep the isKYC active + vm.warp(block.timestamp + appLimits[2] + 1); + address[] memory users = new address[](1); + users[0] = _user; + IKintoID.MonitorUpdateData[][] memory updates = new IKintoID.MonitorUpdateData[][](1); + updates[0] = new IKintoID.MonitorUpdateData[](1); + updates[0][0] = IKintoID.MonitorUpdateData(true, true, 5); + vm.prank(_kycProvider); + _kintoID.monitor(users, updates); + + // increment one more time + _incrementCounterTxs(1, address(counter)); + + // check limits + (operationCount, lastOperationTime, ethCostCount, costLimitLastOperationTime) = + _paymaster.appUserLimit(address(_kintoWallet), address(counter)); + assertTrue(operationCount > 0); + assertEq(lastOperationTime, block.timestamp); + assertTrue(ethCostCount > 0); + assertEq(costLimitLastOperationTime, block.timestamp); + } + function testValidatePaymasterUserOp_WithinTxRateLimit() public { // fixme: once _setOperationCount works fine, refactor and use _setOperationCount; @@ -512,19 +563,18 @@ contract SponsorPaymasterTest is KintoWalletTest { uint256[4] memory appLimits = _kintoAppRegistry.getContractLimits(address(counter)); uint256 estimatedGasPerTx = 0; uint256 cumulativeGasUsed = 0; + UserOperation[] memory userOps = new UserOperation[](1); while (cumulativeGasUsed < appLimits[3]) { if (cumulativeGasUsed + estimatedGasPerTx >= appLimits[3]) return amt; userOps[0] = _incrementCounterOps(1, app)[0]; // generate 1 user op + uint256 beforeGas = gasleft(); _entryPoint.handleOps(userOps, payable(_owner)); // execute the op - uint256 afterGas = gasleft(); - if (amt == 0) estimatedGasPerTx = (beforeGas - afterGas); + + if (amt == 0) estimatedGasPerTx = (beforeGas - gasleft()); cumulativeGasUsed += estimatedGasPerTx; amt++; } } - - //// events - event PostOpRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason); } diff --git a/test/harness/KintoAppRegistryHarness.sol b/test/harness/KintoAppRegistryHarness.sol new file mode 100644 index 000000000..dd6cae8cc --- /dev/null +++ b/test/harness/KintoAppRegistryHarness.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +import "../../src/interfaces/IKintoEntryPoint.sol"; +import "../../src/interfaces/IKintoID.sol"; +import "../../src/interfaces/IKintoAppRegistry.sol"; + +import {KintoAppRegistry} from "../../src/apps/KintoAppRegistry.sol"; + +// Harness contract to expose internal functions for testing. +contract KintoAppRegistryHarness is KintoAppRegistry { + constructor(IKintoWalletFactory _walletFactory) KintoAppRegistry(_walletFactory) {} + + function exposed_baseURI() public pure returns (string memory) { + return _baseURI(); + } +} diff --git a/test/helpers/AASetup.sol b/test/helpers/AASetup.sol index 32ba40eb2..98d405cbb 100644 --- a/test/helpers/AASetup.sol +++ b/test/helpers/AASetup.sol @@ -16,7 +16,7 @@ abstract contract AASetup is Create2Helper, ArtifactsReader { function _checkAccountAbstraction() internal returns ( - KintoID _kintoIDv1, + KintoID _kintoID, EntryPoint _entryPoint, KintoWalletFactory _walletFactory, SponsorPaymaster _sponsorPaymaster @@ -28,7 +28,7 @@ abstract contract AASetup is Create2Helper, ArtifactsReader { console.log("Kinto ID proxy not deployed at", address(kintoProxyAddr)); revert("Kinto ID not deployed"); } - _kintoIDv1 = KintoID(address(kintoProxyAddr)); + _kintoID = KintoID(address(kintoProxyAddr)); // Entry Point address entryPointAddr = _getChainDeployment("EntryPoint"); if (!isContract(entryPointAddr)) { diff --git a/test/helpers/AATestScaffolding.sol b/test/helpers/AATestScaffolding.sol index de8d98fd5..bb3cc6171 100644 --- a/test/helpers/AATestScaffolding.sol +++ b/test/helpers/AATestScaffolding.sol @@ -14,27 +14,35 @@ import "../../src/tokens/EngenCredits.sol"; import "../../src/paymasters/SponsorPaymaster.sol"; import "../../src/wallet/KintoWallet.sol"; import "../../src/wallet/KintoWalletFactory.sol"; +import "../../src/viewers/KYCViewer.sol"; +import "../../src/Faucet.sol"; import "../helpers/UUPSProxy.sol"; import "../helpers/KYCSignature.sol"; import {KintoWalletHarness} from "../harness/KintoWalletHarness.sol"; import {SponsorPaymasterHarness} from "../harness/SponsorPaymasterHarness.sol"; +import {KintoAppRegistryHarness} from "../harness/KintoAppRegistryHarness.sol"; abstract contract AATestScaffolding is KYCSignature { IKintoEntryPoint _entryPoint; + // Kinto Registry KintoAppRegistry _kintoAppRegistry; + // Kinto ID KintoID _implementation; - KintoID _kintoIDv1; + KintoID _kintoID; // Wallet & Factory KintoWalletFactory _walletFactory; KintoWallet _kintoWalletImpl; IKintoWallet _kintoWallet; + // Others EngenCredits _engenCredits; SponsorPaymaster _paymaster; + KYCViewer _kycViewer; + Faucet _faucet; // proxies UUPSProxy _proxy; @@ -42,6 +50,8 @@ abstract contract AATestScaffolding is KYCSignature { UUPSProxy _proxyPaymaster; UUPSProxy _proxyCredit; UUPSProxy _proxyRegistry; + UUPSProxy _proxyKYCViewer; + UUPSProxy _proxyFaucet; function deployAAScaffolding(address _owner, uint256 _ownerPk, address _kycProvider, address _recoverer) public { // Deploy Kinto ID @@ -65,6 +75,12 @@ abstract contract AATestScaffolding is KYCSignature { // Deploy Engen Credits deployEngenCredits(_owner); + // Deploy KYC Viewer + deployKYCViewer(_owner); + + // Deploy Faucet + deployFaucet(_owner); + vm.prank(_owner); // deploy latest KintoWallet version through wallet factory and initialize it @@ -88,11 +104,11 @@ abstract contract AATestScaffolding is KYCSignature { _proxy = new UUPSProxy{salt: 0}(address(_implementation), ""); // wrap in ABI to support easier calls - _kintoIDv1 = KintoID(address(_proxy)); + _kintoID = KintoID(address(_proxy)); // Initialize _proxy - _kintoIDv1.initialize(); - _kintoIDv1.grantRole(_kintoIDv1.KYC_PROVIDER_ROLE(), _kycProvider); + _kintoID.initialize(); + _kintoID.grantRole(_kintoID.KYC_PROVIDER_ROLE(), _kycProvider); vm.stopPrank(); } @@ -100,7 +116,7 @@ abstract contract AATestScaffolding is KYCSignature { vm.startPrank(_owner); // deploy wallet implementation (temporary because of loop dependency on app) - _kintoWalletImpl = new KintoWallet{salt: 0}(_entryPoint, _kintoIDv1, KintoAppRegistry(address(0))); + _kintoWalletImpl = new KintoWallet{salt: 0}(_entryPoint, _kintoID, KintoAppRegistry(address(0))); // deploy wallet factory implementation address _factoryImpl = address(new KintoWalletFactory{salt: 0}(_kintoWalletImpl)); @@ -108,7 +124,7 @@ abstract contract AATestScaffolding is KYCSignature { _walletFactory = KintoWalletFactory(address(_proxyFactory)); // initialize wallet factory - _walletFactory.initialize(_kintoIDv1); + _walletFactory.initialize(_kintoID); // set the wallet factory in the entry point _entryPoint.setWalletFactory(address(_walletFactory)); @@ -172,18 +188,54 @@ abstract contract AATestScaffolding is KYCSignature { // Deploy a new wallet implementation an upgrade // Deploy wallet implementation - _kintoWalletImpl = new KintoWallet{salt: 0}(_entryPoint, _kintoIDv1, _kintoAppRegistry); + _kintoWalletImpl = new KintoWallet{salt: 0}(_entryPoint, _kintoID, _kintoAppRegistry); _walletFactory.upgradeAllWalletImplementations(_kintoWalletImpl); vm.stopPrank(); } + function deployKYCViewer(address _owner) public { + vm.startPrank(_owner); + + // deploy the Kinto KYC Viewer + _kycViewer = new KYCViewer{salt: 0}(address(_walletFactory)); + + // deploy _proxy contract and point it to _implementation + _proxyKYCViewer = new UUPSProxy{salt: 0}(address(_kycViewer), ""); + + // wrap in ABI to support easier calls + _kycViewer = KYCViewer(address(_proxyKYCViewer)); + + // initialize proxy + _kycViewer.initialize(); + + vm.stopPrank(); + } + + function deployFaucet(address _owner) public { + vm.startPrank(_owner); + + // deploy the Kinto KYC Viewer + _faucet = new Faucet{salt: 0}(address(_walletFactory)); + + // deploy _proxy contract and point it to _implementation + _proxyFaucet = new UUPSProxy{salt: 0}(address(_faucet), ""); + + // wrap in ABI to support easier calls + _faucet = Faucet(payable(address(_proxyFaucet))); + + // initialize proxy + _faucet.initialize(); + + vm.stopPrank(); + } + function approveKYC(address _kycProvider, address _account, uint256 _accountPk) public { vm.startPrank(_kycProvider); IKintoID.SignatureData memory sigdata = - _auxCreateSignature(_kintoIDv1, _account, _account, _accountPk, block.timestamp + 1000); + _auxCreateSignature(_kintoID, _account, _accountPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](0); - _kintoIDv1.mintIndividualKyc(sigdata, traits); + _kintoID.mintIndividualKyc(sigdata, traits); vm.stopPrank(); } @@ -192,8 +244,8 @@ abstract contract AATestScaffolding is KYCSignature { vm.startPrank(_kycProvider); IKintoID.SignatureData memory sigdata = - _auxCreateSignature(_kintoIDv1, _account, _account, _accountPk, block.timestamp + 1000); - _kintoIDv1.mintIndividualKyc(sigdata, traits); + _auxCreateSignature(_kintoID, _account, _accountPk, block.timestamp + 1000); + _kintoID.mintIndividualKyc(sigdata, traits); vm.stopPrank(); } @@ -202,8 +254,8 @@ abstract contract AATestScaffolding is KYCSignature { vm.startPrank(_kycProvider); IKintoID.SignatureData memory sigdata = - _auxCreateSignature(_kintoIDv1, _account, _account, _accountPk, block.timestamp + 1000); - _kintoIDv1.burnKYC(sigdata); + _auxCreateSignature(_kintoID, _account, _accountPk, block.timestamp + 1000); + _kintoID.burnKYC(sigdata); vm.stopPrank(); } @@ -327,13 +379,17 @@ abstract contract AATestScaffolding is KYCSignature { } function useHarness() public { - KintoWalletHarness _impl = new KintoWalletHarness(_entryPoint, _kintoIDv1, _kintoAppRegistry); + KintoWalletHarness _impl = new KintoWalletHarness(_entryPoint, _kintoID, _kintoAppRegistry); vm.prank(_walletFactory.owner()); _walletFactory.upgradeAllWalletImplementations(_impl); SponsorPaymasterHarness _paymasterImpl = new SponsorPaymasterHarness(_entryPoint); vm.prank(_paymaster.owner()); _paymaster.upgradeTo(address(_paymasterImpl)); + + KintoAppRegistryHarness _registryImpl = new KintoAppRegistryHarness(_walletFactory); + vm.prank(_kintoAppRegistry.owner()); + _kintoAppRegistry.upgradeTo(address(_registryImpl)); } ////// helper methods to assert the revert reason on UserOperationRevertReason events //// @@ -378,6 +434,8 @@ abstract contract AATestScaffolding is KYCSignature { // clean revert reason & assert string memory cleanRevertReason = _trimToPrefixAndRemoveTrailingNulls(decodedRevertReason, prefixes); + console.log("left: %s", cleanRevertReason); + console.log("right: %s", string(_reasons[idx])); assertEq(cleanRevertReason, string(_reasons[idx]), "Revert reason does not match"); if (_reasons.length > 1) idx++; // if there's only one reason, we always use the same one diff --git a/test/helpers/KYCSignature.sol b/test/helpers/KYCSignature.sol index e19994698..9a156e48f 100644 --- a/test/helpers/KYCSignature.sol +++ b/test/helpers/KYCSignature.sol @@ -9,28 +9,27 @@ import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "../../src/KintoID.sol"; import "../../src/interfaces/IKintoID.sol"; +import "../../src/interfaces/IFaucet.sol"; abstract contract KYCSignature is Test { using ECDSAUpgradeable for bytes32; using SignatureChecker for address; // Create a test for minting a KYC token - function _auxCreateSignature( - IKintoID _kintoIDv1, - address _signer, - address, /* _account */ - uint256 _privateKey, - uint256 _expiresAt - ) internal view returns (IKintoID.SignatureData memory signData) { + function _auxCreateSignature(IKintoID _kintoID, address _signer, uint256 _privateKey, uint256 _expiresAt) + internal + view + returns (IKintoID.SignatureData memory signData) + { signData = IKintoID.SignatureData({ signer: _signer, - nonce: _kintoIDv1.nonces(_signer), + nonce: _kintoID.nonces(_signer), expiresAt: _expiresAt, signature: "" }); // generate EIP-712 hash - bytes32 eip712Hash = _getEIP712Message(signData, address(_kintoIDv1)); + bytes32 eip712Hash = _getEIP712Message(signData, address(_kintoID)); // sign the hash (uint8 v, bytes32 r, bytes32 s) = vm.sign(_privateKey, eip712Hash); @@ -41,7 +40,23 @@ abstract contract KYCSignature is Test { return signData; } - function _auxDappSignature(IKintoID _kintoIDv1, IKintoID.SignatureData memory signData) + // Create a aux function to create an EIP-191 compliant signature for claiming Kinto ETH from the faucet + function _auxCreateSignature(IFaucet _faucet, address _signer, uint256 _privateKey, uint256 _expiresAt) + internal + view + returns (IFaucet.SignatureData memory signData) + { + bytes32 dataHash = keccak256( + abi.encode(_signer, address(_faucet), _expiresAt, _faucet.nonces(_signer), bytes32(block.chainid)) + ); + bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)); // EIP-191 compliant + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_privateKey, ethSignedMessageHash); + bytes memory signature = abi.encodePacked(r, s, v); + return IFaucet.SignatureData(_signer, _faucet.nonces(_signer), _expiresAt, signature); + } + + function _auxDappSignature(IKintoID _kintoID, IKintoID.SignatureData memory signData) internal view returns (bool) @@ -52,7 +67,7 @@ abstract contract KYCSignature is Test { 0xa8bEb41Cf4721121ea58837eBDbd36169a7F246E, 1, signData.expiresAt, - _kintoIDv1.nonces(signData.signer), + _kintoID.nonces(signData.signer), bytes32(block.chainid) ) ); diff --git a/test/helpers/UserOp.sol b/test/helpers/UserOp.sol index 55c5d3ec4..136d49f1e 100644 --- a/test/helpers/UserOp.sol +++ b/test/helpers/UserOp.sol @@ -34,9 +34,6 @@ abstract contract UserOp is Test { address payable _recoverer = payable(vm.addr(_recovererPk)); address payable _funder = payable(vm.addr(_funderPk)); - // constants - uint256 constant CHAIN_ID = 1; - // gas constants uint256 constant CALL_GAS_LIMIT = 4_000_000; uint256 constant VERIFICATION_GAS_LIMIT = 210_000; @@ -58,7 +55,7 @@ abstract contract UserOp is Test { bytes memory _bytesOp ) internal view returns (UserOperation memory op) { return _createUserOperation( - CHAIN_ID, + block.chainid, _from, _target, 0, @@ -80,7 +77,7 @@ abstract contract UserOp is Test { address _paymaster ) internal view returns (UserOperation memory op) { return _createUserOperation( - CHAIN_ID, + block.chainid, _from, _target, 0, @@ -154,7 +151,7 @@ abstract contract UserOp is Test { address _paymaster ) internal view returns (UserOperation memory op) { op = _createUserOperation( - CHAIN_ID, + block.chainid, _from, address(0), 0, @@ -165,7 +162,7 @@ abstract contract UserOp is Test { [CALL_GAS_LIMIT, MAX_FEE_PER_GAS, MAX_PRIORITY_FEE_PER_GAS] ); op.callData = abi.encodeCall(KintoWallet.executeBatch, (opParams.targets, opParams.values, opParams.bytesOps)); - op.signature = _signUserOp(op, KintoWallet(payable(_from)).entryPoint(), CHAIN_ID, _privateKeyOwners); + op.signature = _signUserOp(op, KintoWallet(payable(_from)).entryPoint(), block.chainid, _privateKeyOwners); } // user ops generators diff --git a/test/wallet/execute/Execute.t.sol b/test/wallet/execute/Execute.t.sol index 2e99833e2..2e9ce66c2 100644 --- a/test/wallet/execute/Execute.t.sol +++ b/test/wallet/execute/Execute.t.sol @@ -2,21 +2,15 @@ pragma solidity ^0.8.18; import "forge-std/console.sol"; -import "../../KintoWallet.t.sol"; - -contract ExecuteTest is KintoWalletTest { - /* ============ One Signer Account Transaction Tests (execute) ============ */ +import "../../SharedSetup.t.sol"; +contract ExecuteTest is SharedSetup { function testExecute_WhenPaymaster() public { - uint256 nonce = _kintoWallet.getNonce(); - bool[] memory flags = new bool[](1); - flags[0] = true; - UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = _createUserOperation( address(_kintoWallet), address(counter), - nonce, + _kintoWallet.getNonce(), privateKeys, abi.encodeWithSignature("increment()"), address(_paymaster) @@ -39,7 +33,6 @@ contract ExecuteTest is KintoWalletTest { UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = userOp; - // execute the transaction via the entry point vm.expectRevert(abi.encodeWithSignature("FailedOp(uint256,string)", 0, "AA21 didn't pay prefund")); _entryPoint.handleOps(userOps, payable(_owner)); } @@ -58,11 +51,35 @@ contract ExecuteTest is KintoWalletTest { abi.encodeWithSignature("increment()") ); - // execute the transaction via the entry point _entryPoint.handleOps(userOps, payable(_owner)); assertEq(counter.count(), 1); } + function testExecute_WhenMultipleOps_WhenPaymaster() public { + uint256 nonce = _kintoWallet.getNonce(); + UserOperation[] memory userOps = new UserOperation[](2); + userOps[0] = _createUserOperation( + address(_kintoWallet), + address(counter), + nonce, + privateKeys, + abi.encodeWithSignature("increment()"), + address(_paymaster) + ); + + userOps[1] = _createUserOperation( + address(_kintoWallet), + address(counter), + nonce + 1, + privateKeys, + abi.encodeWithSignature("increment()"), + address(_paymaster) + ); + + _entryPoint.handleOps(userOps, payable(_owner)); + assertEq(counter.count(), 2); + } + function testExecute_RevertWhen_AppIsNotWhitelisted() public { // remove app from whitelist whitelistApp(address(counter), false); @@ -88,30 +105,4 @@ contract ExecuteTest is KintoWalletTest { assertRevertReasonEq("KW: contract not whitelisted"); assertEq(counter.count(), 0); } - - function testExecute_WhenMultipleOps_WhenPaymaster() public { - uint256 nonce = _kintoWallet.getNonce(); - UserOperation[] memory userOps = new UserOperation[](2); - userOps[0] = _createUserOperation( - address(_kintoWallet), - address(counter), - nonce, - privateKeys, - abi.encodeWithSignature("increment()"), - address(_paymaster) - ); - - userOps[1] = _createUserOperation( - address(_kintoWallet), - address(counter), - nonce + 1, - privateKeys, - abi.encodeWithSignature("increment()"), - address(_paymaster) - ); - - // execute the transaction via the entry point - _entryPoint.handleOps(userOps, payable(_owner)); - assertEq(counter.count(), 2); - } } diff --git a/test/wallet/executeBatch/ExecuteBatch.t.sol b/test/wallet/executeBatch/ExecuteBatch.t.sol index eb5e76020..070f713a3 100644 --- a/test/wallet/executeBatch/ExecuteBatch.t.sol +++ b/test/wallet/executeBatch/ExecuteBatch.t.sol @@ -6,6 +6,192 @@ import "forge-std/console.sol"; import "@aa/interfaces/IEntryPoint.sol"; -import "../../KintoWallet.t.sol"; +import "../../SharedSetup.t.sol"; -contract ExecuteBatchTest is KintoWalletTest {} +contract ExecuteBatchTest is SharedSetup { + function testExecuteBatch_WhenPaymaster() public { + // prep batch + address[] memory targets = new address[](1); + targets[0] = address(counter); + + uint256[] memory values = new uint256[](1); + values[0] = 0; + + // we want to do 3 calls: whitelistApp, increment counter and increment counter2 + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSignature("increment()"); + + OperationParamsBatch memory opParams = OperationParamsBatch({targets: targets, values: values, bytesOps: calls}); + UserOperation memory userOp = _createUserOperation( + address(_kintoWallet), _kintoWallet.getNonce(), privateKeys, opParams, address(_paymaster) + ); + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = userOp; + + _entryPoint.handleOps(userOps, payable(_owner)); + assertEq(counter.count(), 1); + } + + function testExecuteBatch_RevertWhen_NoPaymasterNorPrefund() public { + // prep batch + address[] memory targets = new address[](1); + targets[0] = address(counter); + + uint256[] memory values = new uint256[](1); + values[0] = 0; + + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSignature("increment()"); + + OperationParamsBatch memory opParams = OperationParamsBatch({targets: targets, values: values, bytesOps: calls}); + UserOperation memory userOp = + _createUserOperation(address(_kintoWallet), _kintoWallet.getNonce(), privateKeys, opParams, address(0)); + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = userOp; + + vm.expectRevert(abi.encodeWithSignature("FailedOp(uint256,string)", 0, "AA21 didn't pay prefund")); + _entryPoint.handleOps(userOps, payable(_owner)); + } + + function testExecuteBatch_WhenPrefund() public { + // prefund wallet + vm.deal(address(_kintoWallet), 1 ether); + + // prep batch + address[] memory targets = new address[](1); + targets[0] = address(counter); + + uint256[] memory values = new uint256[](1); + values[0] = 0; + + // we want to do 3 calls: whitelistApp, increment counter and increment counter2 + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSignature("increment()"); + + OperationParamsBatch memory opParams = OperationParamsBatch({targets: targets, values: values, bytesOps: calls}); + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = + _createUserOperation(address(_kintoWallet), _kintoWallet.getNonce(), privateKeys, opParams, address(0)); + + _entryPoint.handleOps(userOps, payable(_owner)); + assertEq(counter.count(), 1); + } + + function testExecuteBatch_WhenMultipleOps_WhenPaymaster() public { + // prep batch + address[] memory targets = new address[](2); + targets[0] = address(counter); + targets[1] = address(counter); + + uint256[] memory values = new uint256[](2); + values[0] = 0; + + // we want to do 2 calls + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSignature("increment()"); + calls[1] = abi.encodeWithSignature("increment()"); + + OperationParamsBatch memory opParams = OperationParamsBatch({targets: targets, values: values, bytesOps: calls}); + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( + address(_kintoWallet), _kintoWallet.getNonce(), privateKeys, opParams, address(_paymaster) + ); + + _entryPoint.handleOps(userOps, payable(_owner)); + assertEq(counter.count(), 2); + } + + function testExecuteBatch_RevertWhen_AppIsNotWhitelisted() public { + // remove app from whitelist + whitelistApp(address(counter), false); + + // prep batch + address[] memory targets = new address[](2); + targets[0] = address(_kintoWallet); + targets[1] = address(counter); + + uint256[] memory values = new uint256[](2); + values[0] = 0; + values[1] = 0; + + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSignature("recoverer()"); + calls[1] = abi.encodeWithSignature("increment()"); + + OperationParamsBatch memory opParams = OperationParamsBatch({targets: targets, values: values, bytesOps: calls}); + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( + address(_kintoWallet), _kintoWallet.getNonce(), privateKeys, opParams, address(_paymaster) + ); + + vm.expectEmit(true, true, true, false); + emit UserOperationRevertReason( + _entryPoint.getUserOpHash(userOps[0]), userOps[0].sender, userOps[0].nonce, bytes("") + ); + vm.recordLogs(); + _entryPoint.handleOps(userOps, payable(_owner)); + assertRevertReasonEq("KW: contract not whitelisted"); + } + + function testExecuteBatch_RevertWhen_LenghtMismatch() public { + // remove app from whitelist + whitelistApp(address(counter), false); + + // prep batch + address[] memory targets = new address[](2); + targets[0] = address(_kintoWallet); + targets[1] = address(counter); + + uint256[] memory values = new uint256[](2); + values[0] = 0; + values[1] = 0; + + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSignature("recoverer()"); + + OperationParamsBatch memory opParams = OperationParamsBatch({targets: targets, values: values, bytesOps: calls}); + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( + address(_kintoWallet), _kintoWallet.getNonce(), privateKeys, opParams, address(_paymaster) + ); + + vm.expectEmit(true, true, true, false); + emit UserOperationRevertReason( + _entryPoint.getUserOpHash(userOps[0]), userOps[0].sender, userOps[0].nonce, bytes("") + ); + vm.recordLogs(); + _entryPoint.handleOps(userOps, payable(_owner)); + assertRevertReasonEq("KW-eb: wrong array length"); + } + + function testExecuteBatch_RevertWhen_LenghtMismatch2() public { + // remove app from whitelist + whitelistApp(address(counter), false); + + // prep batch + address[] memory targets = new address[](1); + targets[0] = address(_kintoWallet); + + uint256[] memory values = new uint256[](2); + values[0] = 0; + values[1] = 0; + + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSignature("recoverer()"); + calls[1] = abi.encodeWithSignature("increment()"); + + OperationParamsBatch memory opParams = OperationParamsBatch({targets: targets, values: values, bytesOps: calls}); + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( + address(_kintoWallet), _kintoWallet.getNonce(), privateKeys, opParams, address(_paymaster) + ); + + vm.expectEmit(true, true, true, false); + emit UserOperationRevertReason( + _entryPoint.getUserOpHash(userOps[0]), userOps[0].sender, userOps[0].nonce, bytes("") + ); + vm.recordLogs(); + _entryPoint.handleOps(userOps, payable(_owner)); + assertRevertReasonEq("KW-eb: wrong array length"); + } +} diff --git a/test/wallet/funder/Funder.t.sol b/test/wallet/funder/Funder.t.sol index dbf30907e..bee65f0bc 100644 --- a/test/wallet/funder/Funder.t.sol +++ b/test/wallet/funder/Funder.t.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.18; import "forge-std/console.sol"; -import "../../KintoWallet.t.sol"; +import "../../SharedSetup.t.sol"; -contract FunderTest is KintoWalletTest { +contract FunderTest is SharedSetup { /* ============ Funder Whitelist ============ */ function testUp() public override { @@ -16,25 +16,50 @@ contract FunderTest is KintoWalletTest { } function testSetFunderWhitelist() public { - vm.startPrank(_owner); address[] memory funders = new address[](1); funders[0] = address(23); - uint256 nonce = _kintoWallet.getNonce(); + bool[] memory flags = new bool[](1); flags[0] = true; - UserOperation memory userOp = _createUserOperation( + + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( address(_kintoWallet), address(_kintoWallet), - nonce, + _kintoWallet.getNonce(), privateKeys, abi.encodeWithSignature("setFunderWhitelist(address[],bool[])", funders, flags), address(_paymaster) ); - UserOperation[] memory userOps = new UserOperation[](1); - userOps[0] = userOp; - // Execute the transaction via the entry point + _entryPoint.handleOps(userOps, payable(_owner)); assertEq(_kintoWallet.isFunderWhitelisted(address(23)), true); - vm.stopPrank(); + } + + function testSetFunderWhitelist_RevertWhen_LengthMismatch() public { + address[] memory funders = new address[](2); + funders[0] = address(23); + funders[1] = address(24); + + bool[] memory flags = new bool[](1); + flags[0] = true; + + UserOperation[] memory userOps = new UserOperation[](1); + userOps[0] = _createUserOperation( + address(_kintoWallet), + address(_kintoWallet), + _kintoWallet.getNonce(), + privateKeys, + abi.encodeWithSignature("setFunderWhitelist(address[],bool[])", funders, flags), + address(_paymaster) + ); + + vm.expectEmit(true, true, true, false); + emit UserOperationRevertReason( + _entryPoint.getUserOpHash(userOps[0]), userOps[0].sender, userOps[0].nonce, bytes("") + ); + vm.recordLogs(); + _entryPoint.handleOps(userOps, payable(_owner)); + assertRevertReasonEq("KW-sfw: invalid array"); } } diff --git a/test/wallet/policy/Policy.t.sol b/test/wallet/policy/Policy.t.sol index 5e80bd92f..985d21fcf 100644 --- a/test/wallet/policy/Policy.t.sol +++ b/test/wallet/policy/Policy.t.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.18; import "forge-std/console.sol"; -import "../../KintoWallet.t.sol"; +import "../../SharedSetup.t.sol"; -contract ResetSignerTest is KintoWalletTest { - /* ============ Signers & Policy Tests ============ */ +contract ResetSignerTest is SharedSetup { + /* ============ Signers & Policy tests ============ */ function testAddingOneSigner() public { vm.startPrank(_owner); @@ -24,7 +24,6 @@ contract ResetSignerTest is KintoWalletTest { UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = userOp; - // execute the transaction via the entry point _entryPoint.handleOps(userOps, payable(_owner)); assertEq(_kintoWallet.owners(1), _user); vm.stopPrank(); @@ -46,7 +45,6 @@ contract ResetSignerTest is KintoWalletTest { UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = userOp; - // execute the transaction via the entry point // @dev handleOps fails silently (does not revert) vm.expectEmit(true, true, true, false); emit UserOperationRevertReason( @@ -71,7 +69,6 @@ contract ResetSignerTest is KintoWalletTest { UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = userOp; - // execute the transaction via the entry point // @dev handleOps fails silently (does not revert) vm.expectEmit(true, true, true, false); emit UserOperationRevertReason( @@ -100,7 +97,6 @@ contract ResetSignerTest is KintoWalletTest { UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = userOp; - // execute the transaction via the entry point // @dev handleOps fails silently (does not revert) vm.expectEmit(true, true, true, false); emit UserOperationRevertReason( @@ -126,7 +122,6 @@ contract ResetSignerTest is KintoWalletTest { UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = userOp; - // execute the transaction via the entry point _entryPoint.handleOps(userOps, payable(_owner)); } @@ -145,7 +140,6 @@ contract ResetSignerTest is KintoWalletTest { address(_paymaster) ); - // execute the transaction via the entry point vm.expectEmit(); emit WalletPolicyChanged(_kintoWallet.ALL_SIGNERS(), _kintoWallet.SINGLE_SIGNER()); _entryPoint.handleOps(userOps, payable(_owner)); @@ -171,7 +165,6 @@ contract ResetSignerTest is KintoWalletTest { ); UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = userOp; - // Execute the transaction via the entry point _entryPoint.handleOps(userOps, payable(_owner)); assertEq(_kintoWallet.owners(1), _user); assertEq(_kintoWallet.owners(2), _user2); @@ -221,7 +214,6 @@ contract ResetSignerTest is KintoWalletTest { _entryPoint.getUserOpHash(userOps[1]), userOps[1].sender, userOps[1].nonce, bytes("") ); - // Execute the transaction via the entry point vm.recordLogs(); _entryPoint.handleOps(userOps, payable(_owner)); diff --git a/test/wallet/recovery/Recovery.t.sol b/test/wallet/recovery/Recovery.t.sol index 834d5bb23..7469c83b9 100644 --- a/test/wallet/recovery/Recovery.t.sol +++ b/test/wallet/recovery/Recovery.t.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.18; import "forge-std/console.sol"; -import "../../KintoWallet.t.sol"; +import "../../SharedSetup.t.sol"; -contract RecoveryTest is KintoWalletTest { - /* ============ Recovery Tests ============ */ +contract RecoveryTest is SharedSetup { + /* ============ Recovery tests ============ */ - function testStartRecovert() public { + function testStartRecovery() public { vm.prank(address(_walletFactory)); _kintoWallet.startRecovery(); assertEq(_kintoWallet.inRecovery(), block.timestamp); @@ -29,16 +29,16 @@ contract RecoveryTest is KintoWalletTest { assertEq(_kintoWallet.inRecovery(), block.timestamp); // mint NFT to new owner and burn old - IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoIDv1, _user, _user, 3, block.timestamp + 1000); + IKintoID.SignatureData memory sigdata = _auxCreateSignature(_kintoID, _user, _userPk, block.timestamp + 1000); uint16[] memory traits = new uint16[](0); vm.startPrank(_kycProvider); - _kintoIDv1.mintIndividualKyc(sigdata, traits); - sigdata = _auxCreateSignature(_kintoIDv1, _owner, _owner, 1, block.timestamp + 1000); - _kintoIDv1.burnKYC(sigdata); + _kintoID.mintIndividualKyc(sigdata, traits); + sigdata = _auxCreateSignature(_kintoID, _owner, 1, block.timestamp + 1000); + _kintoID.burnKYC(sigdata); vm.stopPrank(); - assertEq(_kintoIDv1.isKYC(_user), true); + assertEq(_kintoID.isKYC(_user), true); // pass recovery time vm.warp(block.timestamp + _kintoWallet.RECOVERY_TIME() + 1); @@ -50,7 +50,7 @@ contract RecoveryTest is KintoWalletTest { updates[0][0] = IKintoID.MonitorUpdateData(true, true, 5); vm.prank(_kycProvider); - _kintoIDv1.monitor(users, updates); + _kintoID.monitor(users, updates); vm.prank(address(_walletFactory)); _kintoWallet.completeRecovery(users); @@ -69,7 +69,7 @@ contract RecoveryTest is KintoWalletTest { // approve KYC for _user (mint NFT) approveKYC(_kycProvider, _user, _userPk); - assertEq(_kintoIDv1.isKYC(_user), true); + assertEq(_kintoID.isKYC(_user), true); // pass recovery time vm.warp(block.timestamp + _kintoWallet.RECOVERY_TIME() + 1); @@ -83,7 +83,7 @@ contract RecoveryTest is KintoWalletTest { users[0] = _user; vm.prank(_kycProvider); - _kintoIDv1.monitor(users, updates); + _kintoID.monitor(users, updates); // complete recovery vm.prank(address(_walletFactory)); @@ -101,13 +101,13 @@ contract RecoveryTest is KintoWalletTest { // burn old owner NFT revokeKYC(_kycProvider, _owner, _ownerPk); - assertEq(_kintoIDv1.isKYC(_owner), false); + assertEq(_kintoID.isKYC(_owner), false); // pass recovery time vm.warp(block.timestamp + _kintoWallet.RECOVERY_TIME() + 1); // complete recovery - assertEq(_kintoIDv1.isKYC(_user), false); // new owner is not KYC'd + assertEq(_kintoID.isKYC(_user), false); // new owner is not KYC'd address[] memory users = new address[](1); users[0] = _user; vm.prank(address(_walletFactory)); @@ -125,11 +125,11 @@ contract RecoveryTest is KintoWalletTest { // burn old owner NFT revokeKYC(_kycProvider, _owner, _ownerPk); - assertEq(_kintoIDv1.isKYC(_owner), false); + assertEq(_kintoID.isKYC(_owner), false); // approve KYC for _user (mint NFT) approveKYC(_kycProvider, _user, _userPk); - assertEq(_kintoIDv1.isKYC(_user), true); + assertEq(_kintoID.isKYC(_user), true); // pass recovery time (not enough) vm.warp(block.timestamp + _kintoWallet.RECOVERY_TIME() - 1); @@ -143,7 +143,7 @@ contract RecoveryTest is KintoWalletTest { users[0] = _user; vm.prank(_kycProvider); - _kintoIDv1.monitor(users, updates); + _kintoID.monitor(users, updates); // complete recovery vm.prank(address(_walletFactory)); diff --git a/test/wallet/setAppKey/AppKey.t.sol b/test/wallet/setAppKey/AppKey.t.sol index 01f6804ec..a957109be 100644 --- a/test/wallet/setAppKey/AppKey.t.sol +++ b/test/wallet/setAppKey/AppKey.t.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.18; import "forge-std/console.sol"; -import "../../KintoWallet.t.sol"; +import "../../SharedSetup.t.sol"; -contract AppKeyTest is KintoWalletTest { +contract AppKeyTest is SharedSetup { /* ============ App Key ============ */ function testSetAppKey() public { @@ -18,7 +18,6 @@ contract AppKeyTest is KintoWalletTest { address(_paymaster) ); - // Execute the transaction via the entry point _entryPoint.handleOps(userOps, payable(_owner)); assertEq(_kintoWallet.appSigner(address(counter)), _user); } @@ -39,7 +38,6 @@ contract AppKeyTest is KintoWalletTest { address(_paymaster) ); - // execute the transaction via the entry point address appSignerBefore = _kintoWallet.appSigner(address(_engenCredits)); // @dev handleOps fails silently (does not revert) diff --git a/test/wallet/upgrade/Upgrade.t.sol b/test/wallet/upgrade/Upgrade.t.sol index f2efa8967..5e9bc7c1e 100644 --- a/test/wallet/upgrade/Upgrade.t.sol +++ b/test/wallet/upgrade/Upgrade.t.sol @@ -2,16 +2,16 @@ pragma solidity ^0.8.18; import "forge-std/console.sol"; -import "../../KintoWallet.t.sol"; +import "../../SharedSetup.t.sol"; -contract PolicyTest is KintoWalletTest { - /* ============ Upgrade Tests ============ */ +contract PolicyTest is SharedSetup { + /* ============ Upgrade tests ============ */ // FIXME: I think these upgrade tests are wrong because, basically, the KintoWallet.sol does not have // an upgrade function. The upgrade function is in the UUPSUpgradeable.sol contract and the wallet uses the Beacon proxy. function test_RevertWhen_OwnerCannotUpgrade() public { // deploy a new implementation - KintoWallet _newImplementation = new KintoWallet(_entryPoint, _kintoIDv1, _kintoAppRegistry); + KintoWallet _newImplementation = new KintoWallet(_entryPoint, _kintoID, _kintoAppRegistry); // try calling upgradeTo from _owner wallet to upgrade _owner wallet UserOperation memory userOp = _createUserOperation( @@ -34,14 +34,14 @@ contract PolicyTest is KintoWalletTest { assertRevertReasonEq("Address: low-level call with value failed"); } - function test_RevertWhen_OthersCannotUpgrade() public { + function testUpgradeTo_RevertWhen_CallerIsNotOwner() public { // create a wallet for _user approveKYC(_kycProvider, _user, _userPk); vm.broadcast(_user); IKintoWallet userWallet = _walletFactory.createAccount(_user, _recoverer, 0); // deploy a new implementation - KintoWallet _newImplementation = new KintoWallet(_entryPoint, _kintoIDv1, _kintoAppRegistry); + KintoWallet _newImplementation = new KintoWallet(_entryPoint, _kintoID, _kintoAppRegistry); // try calling upgradeTo from _user wallet to upgrade _owner wallet uint256 nonce = userWallet.getNonce(); @@ -59,7 +59,6 @@ contract PolicyTest is KintoWalletTest { UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = userOp; - // execute the transaction via the entry point // @dev handleOps seems to fail silently (does not revert) vm.expectEmit(true, true, true, false); emit UserOperationRevertReason(_entryPoint.getUserOpHash(userOp), userOp.sender, userOp.nonce, bytes("")); diff --git a/test/wallet/validateSignature/ValidateSignature.t.sol b/test/wallet/validateSignature/ValidateSignature.t.sol index 660bc8598..ad5d8fc2f 100644 --- a/test/wallet/validateSignature/ValidateSignature.t.sol +++ b/test/wallet/validateSignature/ValidateSignature.t.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.18; import "forge-std/console.sol"; -import "../../KintoWallet.t.sol"; +import "../../SharedSetup.t.sol"; -contract ValidateSignatureTest is KintoWalletTest { +contract ValidateSignatureTest is SharedSetup { // constants uint256 constant SIG_VALIDATION_FAILED = 1; uint256 constant SIG_VALIDATION_SUCCESS = 0; @@ -210,7 +210,7 @@ contract ValidateSignatureTest is KintoWalletTest { values[2] = 0; bytes[] memory calls = new bytes[](3); - calls[0] = abi.encodeWithSignature("isFunderWhitelisted()"); + calls[0] = abi.encodeWithSignature("recoverer()"); calls[1] = abi.encodeWithSignature("increment()"); calls[2] = abi.encodeWithSignature("increment()"); @@ -249,7 +249,7 @@ contract ValidateSignatureTest is KintoWalletTest { values[2] = 0; bytes[] memory calls = new bytes[](3); - calls[0] = abi.encodeWithSignature("isFunderWhitelisted()"); + calls[0] = abi.encodeWithSignature("recoverer()"); calls[1] = abi.encodeWithSignature("increment()"); calls[2] = abi.encodeWithSignature("increment()"); @@ -290,7 +290,7 @@ contract ValidateSignatureTest is KintoWalletTest { values[2] = 0; bytes[] memory calls = new bytes[](3); - calls[0] = abi.encodeWithSignature("isFunderWhitelisted()"); + calls[0] = abi.encodeWithSignature("recoverer()"); calls[1] = abi.encodeWithSignature("increment()"); calls[2] = abi.encodeWithSignature("increment()"); @@ -339,7 +339,7 @@ contract ValidateSignatureTest is KintoWalletTest { values[3] = 0; bytes[] memory calls = new bytes[](4); - calls[0] = abi.encodeWithSignature("isFunderWhitelisted()"); + calls[0] = abi.encodeWithSignature("recoverer()"); calls[1] = abi.encodeWithSignature("increment()"); calls[2] = abi.encodeWithSignature("increment()"); calls[3] = abi.encodeWithSignature("increment()"); @@ -374,7 +374,7 @@ contract ValidateSignatureTest is KintoWalletTest { values[2] = 0; bytes[] memory calls = new bytes[](3); - calls[0] = abi.encodeWithSignature("isFunderWhitelisted()"); + calls[0] = abi.encodeWithSignature("recoverer()"); calls[1] = abi.encodeWithSignature("increment()"); calls[2] = abi.encodeWithSignature("increment()"); @@ -409,7 +409,7 @@ contract ValidateSignatureTest is KintoWalletTest { bytes[] memory calls = new bytes[](limit + 2); for (uint256 i = 0; i < limit + 2; i++) { - calls[i] = abi.encodeWithSignature("isFunderWhitelisted()"); + calls[i] = abi.encodeWithSignature("recoverer()"); } calls[limit + 1] = abi.encodeWithSignature("increment()"); @@ -657,7 +657,7 @@ contract ValidateSignatureTest is KintoWalletTest { values[i] = 0; if (i == order) { targets[i] = address(_kintoWallet); - calls[i] = abi.encodeWithSignature("isFunderWhitelisted()"); + calls[i] = abi.encodeWithSignature("recoverer()"); } else { targets[i] = address(counter); calls[i] = abi.encodeWithSignature("increment()"); @@ -702,7 +702,7 @@ contract ValidateSignatureTest is KintoWalletTest { values[i] = 0; if (i == order) { targets[i] = address(_kintoWallet); - calls[i] = abi.encodeWithSignature("isFunderWhitelisted()"); + calls[i] = abi.encodeWithSignature("recoverer()"); } else { targets[i] = address(counter); calls[i] = abi.encodeWithSignature("increment()"); diff --git a/test/wallet/whitelist/Whitelist.t.sol b/test/wallet/whitelist/Whitelist.t.sol index 6214d6c92..186367e7b 100644 --- a/test/wallet/whitelist/Whitelist.t.sol +++ b/test/wallet/whitelist/Whitelist.t.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.18; import "forge-std/console.sol"; -import "../../KintoWallet.t.sol"; +import "../../SharedSetup.t.sol"; -contract WhitelistTest is KintoWalletTest { +contract WhitelistTest is SharedSetup { /* ============ Whitelist ============ */ function testWhitelistApp() public { @@ -18,7 +18,6 @@ contract WhitelistTest is KintoWalletTest { privateKeys, address(_kintoWallet), _kintoWallet.getNonce(), address(counter), address(_paymaster) ); - // execute the transaction via the entry point _entryPoint.handleOps(userOps, payable(_owner)); assertTrue(_kintoWallet.appWhitelist(address(counter)));