Skip to content

Conversation

@mgretzke
Copy link
Collaborator

@mgretzke mgretzke commented Dec 16, 2025

Pull Request

Description

Allows to allocate additional amounts that are not part of the deposit in the prepare/execute allocation flow, as well as in the permit2Allocation flow.

For the OnChainAllocator, the contract itself will ensure these amounts are unallocated at the time of the call.

For the HybridAllocator, you will have to provide a signature from the off chain signers that confirms these additional amounts are still unallocated. During the prepare and execute flow, this additionally requires the off chain signer to provide a valid nonce that can be used for this, which is required to make the claimHash predictable.

We can be sure of the users intentions for this additional allocation, since the claimHash must be registered on chain.

@mgretzke mgretzke requested review from 0age and ccashwell December 16, 2025 18:10
@mgretzke mgretzke marked this pull request as ready for review December 16, 2025 18:17
@mgretzke mgretzke requested a review from a team as a code owner December 16, 2025 18:17
Copy link
Contributor

@0age 0age left a comment

Choose a reason for hiding this comment

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

two possible issues and a micro-optimization, take a look but otherwise LGTM for the POC!


// Confirm the allocation - calculate balance differences
for { let i := 0 } lt(i, permittedLength) { i := add(i, 1) } {
let commitmentMemLoc := mload(add(commitmentsContent, mul(i, 0x20))) // load the absolute pointer to the Lock struct
Copy link
Contributor

Choose a reason for hiding this comment

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

micro-optimization: instead of incrementing by one and then multiplying i by 0x20 every time in the loop, you can derive permittedLengthWords by doing one shl 5 and then iterate by 0x20 and just use i directly

diffBalance := add(diffBalance, additionalCommitmentAmount)

// Set the containsAdditionalCommitments flag if the additional commitment amount is not zero
containsAdditionalCommitments := or(containsAdditionalCommitments, gt(additionalCommitmentAmount, 0))
Copy link
Contributor

Choose a reason for hiding this comment

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

i believe iszero(iszero(additionalCommitmentAmount)) is a tad cheaper / simpler

mstore(add(previousBalancesPointer, mul(i, 0x20)), oldBalance)

// Add the additional commitment amount.
diffBalance := add(diffBalance, additionalCommitmentAmount)
Copy link
Contributor

Choose a reason for hiding this comment

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

could you overflow this by providing a massive additional commitment amount?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good question. My initial thought is, that it would not really matter, since it would only just worst case shrink the diffBalance? And then you would just allocate less tokens then you deposited I guess? Additionally, the claim hash needs to add up in the end...
Will need to think about this more though, especially since the HybridAllocator and the OnChainAllocator handle the actual allocation part differently.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Additionally (at least in the case of the hybrid allocator), the off chain signer of the allocator would have needed to sign for this:

_validateContext(commitments, additionalCommitmentAmounts, claimHash, allocationContext.signature);

For the on chain allocator, we either revert here due to the overflow, or due to the fact that the previous balance was not sufficient:

uint256 requiredBalance = allocatedBalance + additionalCommitmentAmounts[i];

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So while this might not affect our current implementations, it does seems like a good check to improve the security on this library itself to minimize unexpected behavior!

AL.prepareAllocation(nonce88, recipient, idsAndAmounts, arbiter, expires, typehash, witness, ALLOCATOR_ID);
if (context.length > 0) {
// Potential off chain nonce provided
HybridAllocationContext calldata allocationContext = _decodeContext(context);
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure whether or not this is going to work; do we have any tests of this functionality yet? the decode-bytes-to-calldata logic is always a little gnarly/counterintuitive

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for looking so detailed at this!
Had all the tests and some tiny fixes committed, but forgot to push this.
I believe this is working as intended, I came up with a similar design for the ERC7683 order data parsing.

This is the setup and the test for this:

function _encodeHybridAllocationContext(uint256 nonce, bytes memory signature)

function test_prepareAndExecuteAllocation_withAdditionalCommitments() public {

I don't have any tests that check the revert scenarios on this _decodeContext function yet though.

mstore(add(previousBalancesPointer, mul(i, 0x20)), oldBalance)

// Add the additional commitment amount to the difference in balance. This amount must be verifiably unallocated.
diffBalance := add(diffBalance, additionalCommitmentAmount)
Copy link
Contributor

Choose a reason for hiding this comment

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

is it possible to overflow this value by providing a massive additionalCommitmentAmount?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See answer here:
#40 (comment)

let previousBalancesPointer := add(previousBalances, 0x20)

// Confirm the allocation - calculate balance differences
for { let i := 0 } lt(i, permittedLength) { i := add(i, 1) } {
Copy link
Contributor

Choose a reason for hiding this comment

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

micro-optimization: for this and some other low-level iterators, we could modify the length once via a mul 0x20 (or shl 5) and then increment i by 0x20 to save all the internal muls

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.

3 participants