diff --git a/test/foundry/abstract/OffchainAssetReceiptVaultTest.sol b/test/foundry/abstract/OffchainAssetReceiptVaultTest.sol index 104706d6..616427f7 100644 --- a/test/foundry/abstract/OffchainAssetReceiptVaultTest.sol +++ b/test/foundry/abstract/OffchainAssetReceiptVaultTest.sol @@ -5,6 +5,7 @@ import {Test, Vm} from "forge-std/Test.sol"; import {ICloneableFactoryV2} from "rain.factory/interface/ICloneableFactoryV2.sol"; import {CloneFactory} from "rain.factory/concrete/CloneFactory.sol"; import { + OffchainAssetReceiptVaultConfig, OffchainAssetReceiptVault, ReceiptVaultConstructionConfig } from "contracts/concrete/vault/OffchainAssetReceiptVault.sol"; @@ -12,6 +13,8 @@ import {LibOffchainAssetVaultCreator} from "../lib/LibOffchainAssetVaultCreator. import {Receipt as ReceiptContract} from "contracts/concrete/receipt/Receipt.sol"; contract OffchainAssetReceiptVaultTest is Test { + event OffchainAssetReceiptVaultInitialized(address sender, OffchainAssetReceiptVaultConfig config); + ICloneableFactoryV2 internal immutable iFactory; OffchainAssetReceiptVault internal immutable iImplementation; ReceiptContract internal immutable receiptImplementation; @@ -30,4 +33,22 @@ contract OffchainAssetReceiptVaultTest is Test { { return LibOffchainAssetVaultCreator.createVault(iFactory, iImplementation, admin, name, symbol); } + + function getReceipt(Vm.Log[] memory logs) internal pure returns (ReceiptContract) { + // Find the OffchainAssetReceiptVaultInitialized event log + address receiptAddress = address(0); + bool eventFound = false; // Flag to indicate whether the event log was found + for (uint256 i = 0; i < logs.length; i++) { + if (logs[i].topics[0] == OffchainAssetReceiptVaultInitialized.selector) { + // Decode the event data + (, OffchainAssetReceiptVaultConfig memory config) = + abi.decode(logs[i].data, (address, OffchainAssetReceiptVaultConfig)); + receiptAddress = config.receiptVaultConfig.receipt; + eventFound = true; // Set the flag to true since event log was found + break; + } + } + // Return an receipt contract + return ReceiptContract(receiptAddress); + } } diff --git a/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.confiscateReceipt.t.sol b/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.confiscateReceipt.t.sol index e1a67607..7760a650 100644 --- a/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.confiscateReceipt.t.sol +++ b/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.confiscateReceipt.t.sol @@ -10,31 +10,6 @@ import {Receipt as ReceiptContract} from "../../../../../contracts/concrete/rece contract ConfiscateReceiptTest is OffchainAssetReceiptVaultTest { event ConfiscateReceipt(address sender, address confiscatee, uint256 id, uint256 confiscated, bytes justification); - event OffchainAssetReceiptVaultInitialized(address sender, OffchainAssetReceiptVaultConfig config); - - /// Get Receipt from event - function getReceipt() internal returns (ReceiptContract) { - Vm.Log[] memory logs = vm.getRecordedLogs(); - - // Find the OffchainAssetReceiptVaultInitialized event log - address receiptAddress = address(0); - bool eventFound = false; // Flag to indicate whether the event log was found - for (uint256 i = 0; i < logs.length; i++) { - if (logs[i].topics[0] == OffchainAssetReceiptVaultInitialized.selector) { - // Decode the event data - (, OffchainAssetReceiptVaultConfig memory config) = - abi.decode(logs[i].data, (address, OffchainAssetReceiptVaultConfig)); - receiptAddress = config.receiptVaultConfig.receipt; - eventFound = true; // Set the flag to true since event log was found - break; - } - } - - // Assert that the event log was found - assertTrue(eventFound, "OffchainAssetReceiptVaultInitialized event log not found"); - // Return an receipt contract - return ReceiptContract(receiptAddress); - } /// Checks that confiscateReceipt balances don't change or do change as expected function checkConfiscateReceipt( @@ -91,7 +66,9 @@ contract ConfiscateReceiptTest is OffchainAssetReceiptVaultTest { // Start recording logs vm.recordLogs(); OffchainAssetReceiptVault vault = createVault(alice, assetName, assetSymbol); - ReceiptContract receipt = getReceipt(); + Vm.Log[] memory logs = vm.getRecordedLogs(); + ReceiptContract receipt = getReceipt(logs); + // Prank as Alice to grant role vm.startPrank(alice); @@ -117,6 +94,7 @@ contract ConfiscateReceiptTest is OffchainAssetReceiptVaultTest { // Ensure the fuzzed key is within the valid range for secp256k1 address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1); address bob = vm.addr((fuzzedKeyBob % (SECP256K1_ORDER - 1)) + 1); + vm.assume(alice != bob); blockNumber = bound(blockNumber, 0, type(uint256).max); vm.roll(blockNumber); @@ -126,11 +104,11 @@ contract ConfiscateReceiptTest is OffchainAssetReceiptVaultTest { // Assume that assets is less than uint256 max assets = bound(assets, 1, type(uint256).max); + // Start recording logs vm.recordLogs(); OffchainAssetReceiptVault vault = createVault(alice, assetName, assetName); - ReceiptContract receipt = getReceipt(); - vm.assume(alice != bob); + Vm.Log[] memory logs = vm.getRecordedLogs(); // Prank as Alice to set roles vm.startPrank(alice); @@ -146,7 +124,7 @@ contract ConfiscateReceiptTest is OffchainAssetReceiptVaultTest { vault.deposit(assets, alice, minShareRatio, data); - checkConfiscateReceipt(vault, receipt, alice, bob, 1, data); + checkConfiscateReceipt(vault, getReceipt(logs), alice, bob, 1, data); vm.stopPrank(); } } diff --git a/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.confiscateShares.t.sol b/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.confiscateShares.t.sol index 08b66e95..63f7b9ce 100644 --- a/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.confiscateShares.t.sol +++ b/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.confiscateShares.t.sol @@ -10,7 +10,6 @@ import {Receipt as ReceiptContract} from "../../../../../contracts/concrete/rece contract ConfiscateSharesTest is OffchainAssetReceiptVaultTest { event ConfiscateShares(address sender, address confiscatee, uint256 confiscated, bytes justification); - event OffchainAssetReceiptVaultInitialized(address sender, OffchainAssetReceiptVaultConfig config); /// Checks that confiscateShares balances don't change or do change as expected function checkConfiscateShares(OffchainAssetReceiptVault vault, address alice, address bob, bytes memory data) diff --git a/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.handler.t.sol b/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.handler.t.sol new file mode 100644 index 00000000..e1eb43f8 --- /dev/null +++ b/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.handler.t.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import {OffchainAssetReceiptVault} from "../../../../../contracts/concrete/vault/OffchainAssetReceiptVault.sol"; +import {OffchainAssetReceiptVaultTest, Vm} from "test/foundry/abstract/OffchainAssetReceiptVaultTest.sol"; +import {LibOffchainAssetVaultCreator} from "test/foundry/lib/LibOffchainAssetVaultCreator.sol"; +import {Receipt as ReceiptContract} from "../../../../../contracts/concrete/receipt/Receipt.sol"; + +contract OffchainAssetReceiptVaultHandlerTest is OffchainAssetReceiptVaultTest { + event SetERC20Tier(address sender, address tier, uint256 minimumTier, uint256[] context, bytes data); + event SetERC1155Tier(address sender, address tier, uint256 minimumTier, uint256[] context, bytes data); + event DepositWithReceipt( + address sender, address owner, uint256 assets, uint256 shares, uint256 id, bytes receiptInformation + ); + + function setUpAddressesAndBounds( + uint256 fuzzedKeyAlice, + uint256 fuzzedKeyBob, + uint256 fuzzedKeyJohn, + uint256 balance, + uint256 referenceBlockNumber, + uint256 certifyUntil + ) internal view returns (address alice, address bob, address john, uint256, uint256, uint256) { + // Ensure the fuzzed key is within the valid range for secp256k + alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1); + bob = vm.addr((fuzzedKeyBob % (SECP256K1_ORDER - 1)) + 1); + john = vm.addr((fuzzedKeyJohn % (SECP256K1_ORDER - 1)) + 1); + + balance = bound(balance, 1, type(uint256).max); // Bound from one to avoid ZeroAssets + referenceBlockNumber = bound(referenceBlockNumber, 1, block.number); + certifyUntil = bound(certifyUntil, 1, type(uint32).max - 1); // substruct 1 for next bound + + return (alice, bob, john, balance, referenceBlockNumber, certifyUntil); + } + + function setUpVault(address alice, string memory assetName, string memory assetSymbol) + internal + returns (OffchainAssetReceiptVault vault, ReceiptContract receipt) + { + vm.recordLogs(); + vault = createVault(alice, assetName, assetSymbol); + Vm.Log[] memory logs = vm.getRecordedLogs(); + receipt = getReceipt(logs); + return (vault, receipt); + } + + /// Test testReceiptTransfer to self with handler role + function testReceiptTransferHandler( + uint256 fuzzedKeyAlice, + uint256 fuzzedKeyBob, + string memory assetName, + uint256 referenceBlockNumber, + uint256 certifyUntil, + uint256 futureTimeStamp, + bool forceUntil, + uint256 balance + ) external { + address alice; + address bob; + (alice, bob,, balance, referenceBlockNumber, certifyUntil) = + setUpAddressesAndBounds(fuzzedKeyAlice, fuzzedKeyBob, 0, balance, referenceBlockNumber, certifyUntil); + + // Need setting future timestamp so system gets unsertified but transfer is possible + // due to a handler role + futureTimeStamp = bound(futureTimeStamp, certifyUntil + 1, type(uint32).max); + + vm.assume(alice != bob); + + OffchainAssetReceiptVault vault; + ReceiptContract receipt; + (vault, receipt) = setUpVault(alice, assetName, assetName); + + // Prank as Alice to grant roles + vm.startPrank(alice); + + vault.grantRole(vault.CERTIFIER(), alice); + vault.grantRole(vault.HANDLER(), bob); + vault.grantRole(vault.DEPOSITOR(), alice); + + // Call the certify function + vault.certify(certifyUntil, referenceBlockNumber, forceUntil, bytes("")); + + vault.deposit(balance, bob, 1, bytes("")); + + vm.stopPrank(); + vm.warp(futureTimeStamp); + + // Prank as Bob + vm.startPrank(bob); + vault.authorizeReceiptTransfer(bob, bob); + receipt.safeTransferFrom(bob, bob, 1, balance, bytes("")); + assertEq(receipt.balanceOf(bob, 1), balance); + + vm.stopPrank(); + } + + /// Test testReceiptTransfer with Owner being a handler + function testReceiptTransferHandlerOwner( + uint256 fuzzedKeyAlice, + uint256 fuzzedKeyBob, + uint256 fuzzedKeyJohn, + string memory assetName, + uint256 referenceBlockNumber, + uint256 certifyUntil, + uint256 futureTimeStamp, + bool forceUntil, + uint256 balance + ) external { + address alice; + address bob; + address john; + (alice, bob, john, balance, referenceBlockNumber, certifyUntil) = setUpAddressesAndBounds( + fuzzedKeyAlice, fuzzedKeyBob, fuzzedKeyJohn, balance, referenceBlockNumber, certifyUntil + ); + + // Need setting future timestamp so system gets uncertified but transfer is possible + // due to a handler role + futureTimeStamp = bound(futureTimeStamp, certifyUntil + 1, type(uint32).max); + + vm.assume(alice != bob); + vm.assume(alice != john); + vm.assume(bob != john); + + OffchainAssetReceiptVault vault; + ReceiptContract receipt; + (vault, receipt) = setUpVault(alice, assetName, assetName); + + // Prank as Alice to grant roles + vm.startPrank(alice); + + vault.grantRole(vault.CERTIFIER(), alice); + vault.grantRole(vault.HANDLER(), bob); + vault.grantRole(vault.DEPOSITOR(), alice); + + // Call the certify function + vault.certify(certifyUntil, referenceBlockNumber, forceUntil, bytes("")); + + // Cannot fuzz assets value due to variable limits + vault.deposit(balance, bob, 1, bytes("")); + + vm.stopPrank(); + vm.warp(futureTimeStamp); + + // Prank as Bob + vm.startPrank(bob); + vault.authorizeReceiptTransfer(bob, john); + receipt.safeTransferFrom(bob, john, 1, balance, bytes("")); + assertEq(receipt.balanceOf(john, 1), balance); + + vm.stopPrank(); + } + + /// Test testReceiptTransfer with Receiver being a handler + function testReceiptTransferHandlerReceiver( + uint256 fuzzedKeyAlice, + uint256 fuzzedKeyBob, + uint256 fuzzedKeyJohn, + string memory assetName, + uint256 referenceBlockNumber, + uint256 certifyUntil, + uint256 futureTimeStamp, + bool forceUntil, + uint256 balance + ) external { + address alice; + address bob; + address john; + (alice, bob, john, balance, referenceBlockNumber, certifyUntil) = setUpAddressesAndBounds( + fuzzedKeyAlice, fuzzedKeyBob, fuzzedKeyJohn, balance, referenceBlockNumber, certifyUntil + ); + + // Need setting future timestamp so system gets uncertified but transfer is possible + // due to a handler role + futureTimeStamp = bound(futureTimeStamp, certifyUntil + 1, type(uint32).max); + + vm.assume(alice != bob); + vm.assume(alice != john); + vm.assume(bob != john); + + OffchainAssetReceiptVault vault; + ReceiptContract receipt; + (vault, receipt) = setUpVault(alice, assetName, assetName); + + // Prank as Alice to grant roles + vm.startPrank(alice); + + vault.grantRole(vault.CERTIFIER(), alice); + vault.grantRole(vault.HANDLER(), john); + vault.grantRole(vault.DEPOSITOR(), alice); + + // Call the certify function + vault.certify(certifyUntil, referenceBlockNumber, forceUntil, bytes("")); + + // Cannot fuzz assets value due to variable limits + vault.deposit(balance, bob, 1, bytes("")); + + vm.stopPrank(); + vm.warp(futureTimeStamp); + + // Prank as Bob + vm.startPrank(bob); + vault.authorizeReceiptTransfer(bob, john); + receipt.safeTransferFrom(bob, john, 1, balance, bytes("")); + assertEq(receipt.balanceOf(john, 1), balance); + + vm.stopPrank(); + } +} diff --git a/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.tiers.t.sol b/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.tiers.t.sol index ed9f3285..db55c0a8 100644 --- a/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.tiers.t.sol +++ b/test/foundry/src/concrete/vault/OffchainAssetReceiptVault.tiers.t.sol @@ -10,36 +10,10 @@ import {OffchainAssetReceiptVaultTest, Vm} from "test/foundry/abstract/OffchainA import {LibOffchainAssetVaultCreator} from "test/foundry/lib/LibOffchainAssetVaultCreator.sol"; import {ITierV2} from "rain.tier.interface/interface/ITierV2.sol"; import {Receipt as ReceiptContract} from "../../../../../contracts/concrete/receipt/Receipt.sol"; -import "forge-std/console.sol"; contract TiersTest is OffchainAssetReceiptVaultTest { event SetERC20Tier(address sender, address tier, uint256 minimumTier, uint256[] context, bytes data); event SetERC1155Tier(address sender, address tier, uint256 minimumTier, uint256[] context, bytes data); - event OffchainAssetReceiptVaultInitialized(address sender, OffchainAssetReceiptVaultConfig config); - - /// Get Receipt from event - function getReceipt() internal returns (ReceiptContract) { - Vm.Log[] memory logs = vm.getRecordedLogs(); - - // Find the OffchainAssetReceiptVaultInitialized event log - address receiptAddress = address(0); - bool eventFound = false; // Flag to indicate whether the event log was found - for (uint256 i = 0; i < logs.length; i++) { - if (logs[i].topics[0] == OffchainAssetReceiptVaultInitialized.selector) { - // Decode the event data - (, OffchainAssetReceiptVaultConfig memory config) = - abi.decode(logs[i].data, (address, OffchainAssetReceiptVaultConfig)); - receiptAddress = config.receiptVaultConfig.receipt; - eventFound = true; // Set the flag to true since event log was found - break; - } - } - - // Assert that the event log was found - assertTrue(eventFound, "OffchainAssetReceiptVaultInitialized event log not found"); - // Return an receipt contract - return ReceiptContract(receiptAddress); - } /// Test setERC20Tier event function testSetERC20Tier( @@ -391,7 +365,7 @@ contract TiersTest is OffchainAssetReceiptVaultTest { // Start recording logs vm.recordLogs(); OffchainAssetReceiptVault vault = createVault(alice, assetName, assetName); - ReceiptContract receipt = getReceipt(); + Vm.Log[] memory logs = vm.getRecordedLogs(); // Prank as Alice to grant roles vm.startPrank(alice); @@ -423,6 +397,7 @@ contract TiersTest is OffchainAssetReceiptVaultTest { ); vault.authorizeReceiptTransfer(bob, alice); + ReceiptContract receipt = getReceipt(logs); receipt.safeTransferFrom(bob, alice, 1, 10, bytes("")); assertEq(receipt.balanceOf(alice, 1), 10);