diff --git a/packages/contracts/package.json b/packages/contracts/package.json index b0905ab..42b41a8 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -4,7 +4,7 @@ "name": "blurpesec", "email": "hahn.michael.f@gmail.com" }, - "version": "0.2.3", + "version": "0.2.4", "private": false, "license": "MIT", "scripts": { diff --git a/packages/contracts/src/codegen/systems/StorageSystemLib.sol b/packages/contracts/src/codegen/systems/StorageSystemLib.sol index 838a81a..15c0703 100644 --- a/packages/contracts/src/codegen/systems/StorageSystemLib.sol +++ b/packages/contracts/src/codegen/systems/StorageSystemLib.sol @@ -52,15 +52,24 @@ library StorageSystemLib { CallWrapper(self.toResourceId(), address(0)).deposit(smartObjectId, bucketId, useOwnerInventory, transferItems); } - function withdraw( + function transferToPlayer( StorageSystemType self, uint256 smartObjectId, bytes32 bucketId, - bool useOwnerInventory, + address recipient, InventoryItemParams[] memory transferItems ) internal { return - CallWrapper(self.toResourceId(), address(0)).withdraw(smartObjectId, bucketId, useOwnerInventory, transferItems); + CallWrapper(self.toResourceId(), address(0)).transferToPlayer(smartObjectId, bucketId, recipient, transferItems); + } + + function withdraw( + StorageSystemType self, + uint256 smartObjectId, + bytes32 bucketId, + InventoryItemParams[] memory transferItems + ) internal { + return CallWrapper(self.toResourceId(), address(0)).withdraw(smartObjectId, bucketId, transferItems); } function internalTransfer( @@ -113,19 +122,37 @@ library StorageSystemLib { : _world().callFrom(self.from, self.systemId, systemCall); } + function transferToPlayer( + CallWrapper memory self, + uint256 smartObjectId, + bytes32 bucketId, + address recipient, + InventoryItemParams[] memory transferItems + ) internal { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert StorageSystemLib_CallingFromRootSystem(); + + bytes memory systemCall = abi.encodeCall( + _transferToPlayer_uint256_bytes32_address_InventoryItemParamsArray.transferToPlayer, + (smartObjectId, bucketId, recipient, transferItems) + ); + self.from == address(0) + ? _world().call(self.systemId, systemCall) + : _world().callFrom(self.from, self.systemId, systemCall); + } + function withdraw( CallWrapper memory self, uint256 smartObjectId, bytes32 bucketId, - bool useOwnerInventory, InventoryItemParams[] memory transferItems ) internal { // if the contract calling this function is a root system, it should use `callAsRoot` if (address(_world()) == address(this)) revert StorageSystemLib_CallingFromRootSystem(); bytes memory systemCall = abi.encodeCall( - _withdraw_uint256_bytes32_bool_InventoryItemParamsArray.withdraw, - (smartObjectId, bucketId, useOwnerInventory, transferItems) + _withdraw_uint256_bytes32_InventoryItemParamsArray.withdraw, + (smartObjectId, bucketId, transferItems) ); self.from == address(0) ? _world().call(self.systemId, systemCall) @@ -172,16 +199,29 @@ library StorageSystemLib { SystemCall.callWithHooksOrRevert(self.from, self.systemId, systemCall, msg.value); } + function transferToPlayer( + RootCallWrapper memory self, + uint256 smartObjectId, + bytes32 bucketId, + address recipient, + InventoryItemParams[] memory transferItems + ) internal { + bytes memory systemCall = abi.encodeCall( + _transferToPlayer_uint256_bytes32_address_InventoryItemParamsArray.transferToPlayer, + (smartObjectId, bucketId, recipient, transferItems) + ); + SystemCall.callWithHooksOrRevert(self.from, self.systemId, systemCall, msg.value); + } + function withdraw( RootCallWrapper memory self, uint256 smartObjectId, bytes32 bucketId, - bool useOwnerInventory, InventoryItemParams[] memory transferItems ) internal { bytes memory systemCall = abi.encodeCall( - _withdraw_uint256_bytes32_bool_InventoryItemParamsArray.withdraw, - (smartObjectId, bucketId, useOwnerInventory, transferItems) + _withdraw_uint256_bytes32_InventoryItemParamsArray.withdraw, + (smartObjectId, bucketId, transferItems) ); SystemCall.callWithHooksOrRevert(self.from, self.systemId, systemCall, msg.value); } @@ -251,15 +291,19 @@ interface _deposit_uint256_bytes32_bool_InventoryItemParamsArray { ) external; } -interface _withdraw_uint256_bytes32_bool_InventoryItemParamsArray { - function withdraw( +interface _transferToPlayer_uint256_bytes32_address_InventoryItemParamsArray { + function transferToPlayer( uint256 smartObjectId, bytes32 bucketId, - bool useOwnerInventory, + address recipient, InventoryItemParams[] memory transferItems ) external; } +interface _withdraw_uint256_bytes32_InventoryItemParamsArray { + function withdraw(uint256 smartObjectId, bytes32 bucketId, InventoryItemParams[] memory transferItems) external; +} + interface _internalTransfer_uint256_bytes32_bytes32_InventoryItemParamsArray { function internalTransfer( uint256 smartObjectId, diff --git a/packages/contracts/src/codegen/world/IStorageSystem.sol b/packages/contracts/src/codegen/world/IStorageSystem.sol index dcb6ab0..5f0ad3a 100644 --- a/packages/contracts/src/codegen/world/IStorageSystem.sol +++ b/packages/contracts/src/codegen/world/IStorageSystem.sol @@ -20,10 +20,16 @@ interface IStorageSystem { InventoryItemParams[] memory transferItems ) external; + function sm_v0_2_0__transferToPlayer( + uint256 smartObjectId, + bytes32 bucketId, + address recipient, + InventoryItemParams[] memory transferItems + ) external; + function sm_v0_2_0__withdraw( uint256 smartObjectId, bytes32 bucketId, - bool useOwnerInventory, InventoryItemParams[] memory transferItems ) external; diff --git a/packages/contracts/src/systems/StorageSystem/StorageSystem.sol b/packages/contracts/src/systems/StorageSystem/StorageSystem.sol index 838b9f1..65d8159 100644 --- a/packages/contracts/src/systems/StorageSystem/StorageSystem.sol +++ b/packages/contracts/src/systems/StorageSystem/StorageSystem.sol @@ -76,16 +76,28 @@ contract StorageSystem is System { } } - function withdraw( + /** + * @notice Withdraw items from a bucket to an ephemeral inventory. + * @param smartObjectId The id of the smart object to withdraw items from. + * @param bucketId The id of the bucket to withdraw items from. + * @param recipient The address of the recipient to receive the withdrawn items in their ephemeral inventory. + * @param transferItems The items to withdraw from the bucket, represented as an array of + * InventoryItemParams structs. + * @dev This function checks if the user is authorized to withdraw items from the bucket, + * and if the items exist in the bucket. If they do exist, + * it transfers them from the bucket to the ephemeral inventory for the specified user address. + * If the item does not exist in the bucket, it reverts. + * It also updates the total metadata for the item in the InventoryBalances table. + */ + function transferToPlayer( uint256 smartObjectId, bytes32 bucketId, - bool useOwnerInventory, + address recipient, InventoryItemParams[] memory transferItems ) public { if (!storeAuthSystem.canWithdraw(smartObjectId, bucketId, _msgSender())) { revert UnauthorizedWithdraw(); } - (, , address transferrer, ) = IWorldWithContext(_world()).getWorldCallContext(1); for (uint i = 0; i < transferItems.length; ) { InventoryItemParams memory item = transferItems[i]; @@ -99,9 +111,19 @@ contract StorageSystem is System { storeLogicSystem._processWithdraw(smartObjectId, bucketId, item); i = unsafe_increment(i); } - if (!useOwnerInventory) { - storeProxySystem.proxyTransferToEphemeral(smartObjectId, transferrer, transferItems); + if (recipient != OwnershipByObject.getAccount(smartObjectId)) { + storeProxySystem.proxyTransferToEphemeral(smartObjectId, recipient, transferItems); + } + } + + function withdraw(uint256 smartObjectId, bytes32 bucketId, InventoryItemParams[] memory transferItems) public { + (, , address msgInitiator, ) = IWorldWithContext(_world()).getWorldCallContext(1); + if (msgInitiator == OwnershipByObject.getAccount(smartObjectId)) { + // owner can always withdraw from their own bucket + transferToPlayer(smartObjectId, bucketId, msgInitiator, transferItems); + return; } + transferToPlayer(smartObjectId, bucketId, msgInitiator, transferItems); } function internalTransfer( diff --git a/packages/contracts/test/StorageManager/StorageManagerEphemeralTest.t.sol b/packages/contracts/test/StorageManager/StorageManagerEphemeralTest.t.sol index 58d6d8f..f442afe 100644 --- a/packages/contracts/test/StorageManager/StorageManagerEphemeralTest.t.sol +++ b/packages/contracts/test/StorageManager/StorageManagerEphemeralTest.t.sol @@ -185,7 +185,7 @@ contract StoragStorageManagerEphemeralTesteManagerTest is SetupTestWithBucketsTe // Test unauthorized Deposit items into bucket vm.startPrank(unauthorizedPlayer); vm.expectRevert(UnauthorizedWithdraw.selector); - world.sm_v0_2_0__withdraw(ssuId, bucketId, false, withdrawTransferItems); + world.sm_v0_2_0__withdraw(ssuId, bucketId, withdrawTransferItems); vm.stopPrank(); // Test authorized account, but unauthorized sender - Deposit items into bucket @@ -198,7 +198,7 @@ contract StoragStorageManagerEphemeralTesteManagerTest is SetupTestWithBucketsTe ) ); vm.resumeGasMetering(); - world.sm_v0_2_0__withdraw(ssuId, bucketId, false, withdrawTransferItems); + world.sm_v0_2_0__withdraw(ssuId, bucketId, withdrawTransferItems); vm.pauseGasMetering(); vm.stopPrank(); @@ -208,7 +208,7 @@ contract StoragStorageManagerEphemeralTesteManagerTest is SetupTestWithBucketsTe vm.stopPrank(); // Authorized Deposit items into bucket vm.startPrank(player); - world.sm_v0_2_0__withdraw(ssuId, bucketId, false, withdrawTransferItems); + world.sm_v0_2_0__withdraw(ssuId, bucketId, withdrawTransferItems); vm.stopPrank(); // Verify the deposit was successful uint64 ephBalanceAfterWithdraw = uint64(EphemeralInvItem.getQuantity(ssuId, player, itemId)); @@ -270,7 +270,7 @@ contract StoragStorageManagerEphemeralTesteManagerTest is SetupTestWithBucketsTe // Test unauthorized Deposit items into bucket vm.startPrank(unauthorizedPlayer); vm.expectRevert(UnauthorizedWithdraw.selector); - world.sm_v0_2_0__withdraw(ssuId, bucketId, false, withdrawTransferItems); + world.sm_v0_2_0__withdraw(ssuId, bucketId, withdrawTransferItems); vm.stopPrank(); // Test authorized account, but unauthorized sender - Deposit items into bucket @@ -283,7 +283,7 @@ contract StoragStorageManagerEphemeralTesteManagerTest is SetupTestWithBucketsTe ) ); vm.resumeGasMetering(); - world.sm_v0_2_0__withdraw(ssuId, bucketId, false, withdrawTransferItems); + world.sm_v0_2_0__withdraw(ssuId, bucketId, withdrawTransferItems); vm.pauseGasMetering(); vm.stopPrank(); @@ -293,7 +293,82 @@ contract StoragStorageManagerEphemeralTesteManagerTest is SetupTestWithBucketsTe vm.stopPrank(); // Authorized Deposit items into bucket vm.startPrank(player); - world.sm_v0_2_0__withdraw(ssuId, bucketId, false, withdrawTransferItems); + world.sm_v0_2_0__withdraw(ssuId, bucketId, withdrawTransferItems); + vm.stopPrank(); + // Verify the deposit was successful + uint64 ephBalanceAfterWithdraw = uint64(EphemeralInvItem.getQuantity(ssuId, player, itemId)); + uint64 metadataQtyAfterWithdraw = InventoryBalances.getQuantity(ssuId, itemId); + assertEq( + metadataQtyAfterWithdraw, + metadataQtyAfterDeposit - totalWithdrawAmount, + "Expected smart object's inventory balances to be smaller after withdraw by `totalWithdrawAmount` amount" + ); + assertEq( + ephBalanceAfterWithdraw, + ephBalanceAfterDeposit + totalWithdrawAmount, + "Expected ephemeral inventory balance to be back to full after withdraw" + ); + // // // expect bucket balance to be 0 + BucketedInventoryItemData memory bucketBalance = BucketedInventoryItem.get(bucketId, itemId); + assertEq(uint64(bucketBalance.quantity), 0, "Expected bucket balance to be 0 after withdraw"); + assertEq( + bucketBalance.exists, + false, + "Expected bucket balance to be cleared after zeroing out" + ); + // transfer items from buckets to ephemeral to zero out inventory balances + + // clear out inventorybalances + bool inventoryBalance = InventoryBalances.getExists(ssuId, itemId); + assertEq( + inventoryBalance, + false, + "Expected smart object's inventory balances to be deleted if it zeroes out" + ); + } + + function testWithdrawFullFromBucketToPlayer() public { + /** Withdraw */ + vm.pauseGasMetering(); + uint64 totalWithdrawAmount = uint64(afterDepositBucketBalance); + InventoryItemParams[] memory withdrawTransferItems = new InventoryItemParams[](1); + withdrawTransferItems[0] = InventoryItemParams({ + smartObjectId: itemId, + quantity: uint64(totalWithdrawAmount) + }); + uint256 startBucketBal = uint64(BucketedInventoryItem.getQuantity(bucketId, itemId)); + assertEq( + startBucketBal, + afterDepositBucketBalance, + "Expected bucket balance to be the same as the total deposit amount before withdraw" + ); + // Test unauthorized Deposit items into bucket + vm.startPrank(unauthorizedPlayer); + vm.expectRevert(UnauthorizedWithdraw.selector); + world.sm_v0_2_0__transferToPlayer(ssuId, bucketId, player, withdrawTransferItems); + vm.stopPrank(); + + // Test authorized account, but unauthorized sender - Deposit items into bucket + vm.startPrank(player); + vm.expectRevert( + abi.encodeWithSelector( + AccessSystemLib.Access_NotDirectOwnerOrCanTransferToEphemeral.selector, + storeProxySystemAddress, + ssuId + ) + ); + vm.resumeGasMetering(); + world.sm_v0_2_0__transferToPlayer(ssuId, bucketId, player, withdrawTransferItems); + vm.pauseGasMetering(); + vm.stopPrank(); + + // Set authorization to system + vm.startPrank(admin); + ephemeralInteractSystem.setTransferToEphemeralAccess(ssuId, storeProxySystemAddress, true); + vm.stopPrank(); + // Authorized Deposit items into bucket + vm.startPrank(player); + world.sm_v0_2_0__transferToPlayer(ssuId, bucketId, player, withdrawTransferItems); vm.stopPrank(); // Verify the deposit was successful uint64 ephBalanceAfterWithdraw = uint64(EphemeralInvItem.getQuantity(ssuId, player, itemId)); diff --git a/packages/contracts/test/StorageManager/StorageManagerPrimaryTest.t.sol b/packages/contracts/test/StorageManager/StorageManagerPrimaryTest.t.sol index 66a153e..48c0800 100644 --- a/packages/contracts/test/StorageManager/StorageManagerPrimaryTest.t.sol +++ b/packages/contracts/test/StorageManager/StorageManagerPrimaryTest.t.sol @@ -152,10 +152,11 @@ contract StorageManagerPrimaryTest is SetupTestWithBucketsTest { afterDepositBucketBalance, "Expected bucket balance to be the same as the total deposit amount before withdraw" ); + address ownerOfSSU = OwnershipByObject.getAccount(ssuId); // Test unauthorized Deposit items into bucket vm.startPrank(unauthorizedPlayer); vm.expectRevert(UnauthorizedWithdraw.selector); - world.sm_v0_2_0__withdraw(ssuId, bucketId, true, withdrawTransferItems); + world.sm_v0_2_0__transferToPlayer(ssuId, bucketId, ownerOfSSU, withdrawTransferItems); vm.stopPrank(); // Test authorized account, but unauthorized sender - Deposit items into bucket @@ -179,7 +180,7 @@ contract StorageManagerPrimaryTest is SetupTestWithBucketsTest { // Authorized Deposit items into bucket uint64 primaryBalanceBeforeWithdraw = uint64(InventoryItem.getQuantity(ssuId, itemId)); vm.startPrank(player); - world.sm_v0_2_0__withdraw(ssuId, bucketId, true, withdrawTransferItems); + world.sm_v0_2_0__transferToPlayer(ssuId, bucketId, ownerOfSSU, withdrawTransferItems); vm.stopPrank(); // Verify the deposit was successful uint64 primaryBalanceAfterWithdraw = uint64(InventoryItem.getQuantity(ssuId, itemId)); @@ -235,10 +236,12 @@ contract StorageManagerPrimaryTest is SetupTestWithBucketsTest { afterDepositBucketBalance, "Expected bucket balance to be the same as the total deposit amount before withdraw" ); + address ownerOfSSU = OwnershipByObject.getAccount(ssuId); + // Test unauthorized Deposit items into bucket vm.startPrank(unauthorizedPlayer); vm.expectRevert(UnauthorizedWithdraw.selector); - world.sm_v0_2_0__withdraw(ssuId, bucketId, true, withdrawTransferItems); + world.sm_v0_2_0__transferToPlayer(ssuId, bucketId, ownerOfSSU, withdrawTransferItems); vm.stopPrank(); // Test authorized account, but unauthorized sender - Deposit items into bucket @@ -262,7 +265,7 @@ contract StorageManagerPrimaryTest is SetupTestWithBucketsTest { // Authorized Deposit items into bucket uint64 primaryBalanceBeforeWithdraw = uint64(InventoryItem.getQuantity(ssuId, itemId)); vm.startPrank(player); - world.sm_v0_2_0__withdraw(ssuId, bucketId, true, withdrawTransferItems); + world.sm_v0_2_0__transferToPlayer(ssuId, bucketId, ownerOfSSU, withdrawTransferItems); vm.stopPrank(); // Verify the deposit was successful uint64 primaryBalanceAfterWithdraw = uint64(InventoryItem.getQuantity(ssuId, itemId)); diff --git a/packages/contracts/test/StorageManager/StoreAuthDelegatedAccessTest.t.sol b/packages/contracts/test/StorageManager/StoreAuthDelegatedAccessTest.t.sol index d8a4efa..05c50da 100644 --- a/packages/contracts/test/StorageManager/StoreAuthDelegatedAccessTest.t.sol +++ b/packages/contracts/test/StorageManager/StoreAuthDelegatedAccessTest.t.sol @@ -64,7 +64,9 @@ contract StoreAuthDelegatedAccessTest is SetupTestWithBucketsTest { assertFalse(canTransfer, "Expected delegated canTransferBucket to return false"); } - function testRegisterDelegatedAccessSystemAndAuthorize() public { + function testProxyTransferSystem() public { + + } }