Advanced Section: Additional Function

This is an in-depth look at the additional function check operation. In order to understand, use and custom the Treasury Manager, it is not necessary to read this page.

How Is the Check Performed?

When a function of the Treasury Manager is called, a check is performed to see if there is an additional function that must be called instead of the original one.

The check is done using the _trySandboxedCall function. This function is internally called by all TreasuryManager external functions to see if there is a more updated function for money management.

For example, you can find it on the transfer function:

function transfer(address token, uint256 value, address receiver, uint256 tokenType, uint256 objectId, bool safe, bool withData, bytes calldata data) external override authorizedOnly returns(bool result, bytes memory returnData) {
    (result, returnData) = _trySandboxedCall(false);  //HERE
    if(result) {
        assembly {
            return(add(returnData, 0x20), mload(returnData))
        }
    }
    (result, returnData) = _transfer(TransferEntry(token, tokenType == 0 ? new uint256[](0) : objectId.asSingletonArray(), tokenType == 1 ? new uint256[](0) : value.asSingletonArray(), receiver, safe, false, withData, data));
}

And in the onERC721Received:

function onERC721Received(address, address, uint256, bytes calldata) external override returns (bytes4) {
    (bool result, bytes memory returnData) = _trySandboxedCall(false); //HERE
    if(result) {
       assembly {
           return(add(returnData, 0x20), mload(returnData))
       }
    }
    return this.onERC721Received.selector;
}

and so on.

For protocol security reasons, the AdditionalFunctionsServerManager contract is external to TreasuryManager. In fact, the _trySandboxedCall function uses the delegatecall to the AdditionalFunctionsServerManager. This means that potentially using the delegatecall, in case of a malicious function could be very dangerous dynamics as an external function could overwrite TreasuryManager parameters. Also for this reason, the AdditionalFunctionsServerManager is placed outside and the TreasuryManager is not an active Component but it’s just attached to the Organization. Even if it was an active Component the only changeable variable in the TreasuryManager could be the Organization variable which represents the Organization to which the TreasuryManager Component is attached.

In addition to the above protection mechanism, there is a second key-based protection mechanism that prevents a delegatecall, called by the _trySandboxedCall, from calling another _trySandboxedCall call and so another delegatecall. This mechanism is what we called avoidReentrancy protection.

The _trySandboxedCall function presents this flow that prevents a malicious external function from creating hazards:

  1. _trySandboxedCall is called by a TreasuryManager function.

  2. The TreasuryManager saves the organization to which it is attached in a precautionary manner.

  3. Every time the AdditionalFunctionsServerManager is called by a _trySandboxedCall, a temporary TreasuryManager key is generated and saved in the AdditionalFunctionsServerManager.

  4. The delegatecall is executed.

  5. The new key is verified to be the same as the temporary key.

  6. The TreasuryManager asked to AdditionalFunctionsServerManager (that at this moment is an Organization Component even if only temporary) to reattach the TreasuryManager as a Component.

  7. The AdditionalFunctionsServerManager is detached as Component .

Last updated