Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(world): add Balance table and BalanceTransferSystem #1425

Merged
merged 11 commits into from
Sep 12, 2023

Conversation

alvrs
Copy link
Member

@alvrs alvrs commented Sep 8, 2023

Fixes #1283
TODO

  • fix WorldContextConsumer
  • add test for WorldContextConsumer / WorldContextProvider with msgSender and msgValue
  • add logic for updating root namespace balance when sending to world without calling a system
  • add test for calling root systems with balance
  • add test for calling non-root systems with balance
  • add test for sending balance to world without calling a system
  • add test for transferring balance between namespaces
  • add test for transferring balance to address

@changeset-bot
Copy link

changeset-bot bot commented Sep 8, 2023

🦋 Changeset detected

Latest commit: 7ffde62

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 28 packages
Name Type
@latticexyz/world Major
@latticexyz/cli Major
@latticexyz/dev-tools Major
@latticexyz/store-sync Major
@latticexyz/store-indexer Major
@latticexyz/abi-ts Major
@latticexyz/block-logs-stream Major
@latticexyz/common Major
@latticexyz/config Major
create-mud Major
@latticexyz/ecs-browser Major
@latticexyz/gas-report Major
@latticexyz/network Major
@latticexyz/noise Major
@latticexyz/phaserx Major
@latticexyz/protocol-parser Major
@latticexyz/react Major
@latticexyz/recs Major
@latticexyz/schema-type Major
@latticexyz/services Major
@latticexyz/solecs Major
solhint-config-mud Major
solhint-plugin-mud Major
@latticexyz/std-client Major
@latticexyz/std-contracts Major
@latticexyz/store-cache Major
@latticexyz/store Major
@latticexyz/utils Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR


// Update the balances
Balances.set(fromNamespace, balance - amount);
payable(toAddress).transfer(amount);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know the "why" but I've seen folks prefer this approach over the above:

(bool success, ) = toAddress.call{value: amount}("");

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bytes16 namespace = resourceSelector.getNamespace();
uint256 currentBalance = Balances.get(namespace);
Balances.set(namespace, currentBalance + value);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anything to be worried about here wrt reentry?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this smells like a re-entry attack

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate how a reentry attack would work here?

This is a utility that is used by call, callFrom and the World's fallback function, in each case just passing the msg.value to the SystemCall util. If the contract that's called in here would call one of these entry points again, it would still only pass that new call's msg.value to the function.

I think we need to update the balance table value before calling the system, because the system might want to transfer the balance somewhere else in this call.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not an expert on this, reentry + managing balances/funds just makes me nervous and so wanted to make sure we've considered it.

I think the important thing is that the balance transfer happens before any code that can be called/overridden/injected by malicious actors? In this case, requireAccess is the only thing I can see but it doesn't make any external calls, so I think it's fine?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, requireAccess only checks an internal table.

Agree this is definitely a critical part of the system that we should be extra careful with and should also point this out as a focus area in the audit.

@holic
Copy link
Member

holic commented Sep 8, 2023

  • do we need to protect against systems receiving value if not called through the world?
    • similarly but not related to this PR, should we prevent systems being called without the world entry point?
  • does the world/root namespace have a way to withdraw all? if so, how should it update its accounting of balances? or will you have to specify the namespace to withdraw from?
  • what if I just send value to the world? do we need to account for that or prevent it?

@ludns
Copy link
Member

ludns commented Sep 8, 2023

you can never prevent someone from sending eth to a contract, a contract can always self destruct and send value and not trigger your fallback

@alvrs
Copy link
Member Author

alvrs commented Sep 10, 2023

  • do we need to protect against systems receiving value if not called through the world?
    • similarly but not related to this PR, should we prevent systems being called without the world entry point?

Calling systems directly is not intended, but I don't think we should protect against it. As long as systems don't hold state it doesn't matter by who they're called since they will read/write on their caller by default. And there might be some advanced custom use cases where systems are limited to one World or do hold some internal state and the developers have some method for updating the internal state by calling them directly.

  • does the world/root namespace have a way to withdraw all? if so, how should it update its accounting of balances? or will you have to specify the namespace to withdraw from?

Whoever has root access can theoretically withdraw everything by deploying a new root system to transfer all the world contract's balance out, but it's basically a rug pull and not intended (we can't prevent it though). In the ideal world namespaces can only manage their own balances, which is the case once root namespace access is burned.

  • what if I just send value to the world? do we need to account for that or prevent it?

Right now the world can receive ETH without being attributed to any namespace via its receive function. We could update the root namespace's balance in that case?

.changeset/pink-tips-give.md Outdated Show resolved Hide resolved
.changeset/pink-tips-give.md Outdated Show resolved Hide resolved
packages/world/src/World.sol Outdated Show resolved Hide resolved
@alvrs alvrs marked this pull request as ready for review September 12, 2023 11:04
@alvrs alvrs requested review from holic, dk1a and ludns September 12, 2023 11:05
Copy link
Member

@holic holic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there ways for funds to get into the world without triggering receive?

If so, do we want some way for the root namespace owner to withdraw address(world).balance - totalOfCurrentBalances as a safety/backstop?

@ludns
Copy link
Member

ludns commented Sep 12, 2023 via email

@alvrs
Copy link
Member Author

alvrs commented Sep 12, 2023

If so, do we want some way for the root namespace owner to withdraw address(world).balance - totalOfCurrentBalances as a safety/backstop?

I'd say because it requires real effort to send balance to the world without triggering receive we don't need to solve for this case (maintaining a totalOfCurrentBalances would increase the gas cost of every system call with value).

For anyone with root access to the World it's still possible to withdraw all balance from the World (with or without adjusting the Balances table) by registering a new root system.

@holic
Copy link
Member

holic commented Sep 12, 2023

For anyone with root access to the World it's still possible to withdraw all balance from the World (with or without adjusting the Balances table by registering a new root system.

How so?

@alvrs
Copy link
Member Author

alvrs commented Sep 12, 2023

How so?

Root systems are delegatecalled and therefore can access the world state directly (including the World contract's balance)

@alvrs alvrs merged commit 2ca75f9 into main Sep 12, 2023
@alvrs alvrs deleted the alvrs/world-balance branch September 12, 2023 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

store value in world instead of system contracts
3 participants