Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
68 changes: 56 additions & 12 deletions packages/contracts/src/codegen/systems/StorageSystemLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion packages/contracts/src/codegen/world/IStorageSystem.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
32 changes: 27 additions & 5 deletions packages/contracts/src/systems/StorageSystem/StorageSystem.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();

Expand All @@ -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));
Expand Down Expand Up @@ -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
Expand All @@ -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();

Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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));
Expand Down Expand Up @@ -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
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ contract StoreAuthDelegatedAccessTest is SetupTestWithBucketsTest {
assertFalse(canTransfer, "Expected delegated canTransferBucket to return false");
}

function testRegisterDelegatedAccessSystemAndAuthorize() public {
function testProxyTransferSystem() public {



}
}