Example of an Updated Function

All--or nearly all--of the Treasury Manager's functions use the _trySandboxedCall function to check if updated versions of themselves are available before they are executed.

The check is done using the function's selector, which identifies the function and its input parameters. If an updated version that corresponds to that selector is present, it will be executed instead.

What follows are two examples that demonstrate how to extend a Treasury Manager functionality.

Example of a New Function Limiting the Current One

Here, the Treasury Manager is using the standard onERC721Received function, which handles receiving ERC721s.

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

Let's say you want to update the ERC721Received function to inhibit the receival of ERC721s, so that the Treasury Manager, and therefore the Organization, can no longer receive any at all.

For this, setAdditionalFunction is used. As input, it takes the selector (bytes4), the server (address) and the log (bool) parameters.

function setAdditionalFunction(bytes4 selector, address newServer, bool log) external override authorizedOnly returns (address oldServer);

In this case, the newServer contract contains the new onERC721Received function that inhibits the receival of ERC721s:

pragma solidity ^0.8.0;
contract TestContract {
    function onERC721Received(address, address, uint256, bytes calldata b) external returns (bytes4) {
        revert("ERC721 receive temporary not allowed");
    }
}

The selector of the function is retrieved:

var selector = web3.utils.sha3("onERC721Received(address,address,uint256,bytes)").substring(0, 10) + utilities.voidBytes32.substring(10);

To be called correctly, the selector of the new function must be the same as the original one. Naturally, only the selector must be the same; the content of the function can be different.

Now, setAdditionalFunction can be called. Every time an ERC721 token arrives in the Treasury Manager, the new onERC721Received function will be automatically called, and the transaction reverts.

Here's an example of the entire integration code, with some useful checks:

pragma solidity ^0.8.0;
contract TestContract {
    function onERC721Received(address, address, uint256, bytes calldata b) external returns (bytes4) {
        revert("ERC721 receive temporary not allowed");
    }
}`, 'TestContract');

        var testContract = await new web3.eth.Contract(TestContract.abi).deploy({data : TestContract.bin}).send(blockchainConnection.getSendingOptions());

        var selector = web3.utils.sha3("onERC721Received(address,address,uint256,bytes)").substring(0, 10) + utilities.voidBytes32.substring(10);

        var transaction = await manager.methods.setAdditionalFunction(selector.substring(0, 10), testContract.options.address, true).send(organization.asActiveComponent);
        var logs = (await web3.eth.getTransactionReceipt(transaction.transactionHash)).logs;
        logs = logs.filter(it => it.topics[0] === web3.utils.sha3('AdditionalFunction(address,bytes4,address,address)') && it.topics[1] === selector && it.topics[2] === web3.eth.abi.encodeParameter("address", utilities.voidEthereumAddress) && it.topics[3] === web3.eth.abi.encodeParameter("address", testContract.options.address));
        assert.equal(1, logs.length);
        
        //the TreasuryManager can no longer accept ERC721 tokens
        objectId = await erc721.methods.lastId().call();
        await erc721.methods.mint(accounts[0]).send(blockchainConnection.getSendingOptions());

        await catchCall(erc721.methods.safeTransferFrom(accounts[0], manager.options.address, objectId), "ERC721 receive temporary not allowed");

Example of a New Function Extending the Current One

Here, the Treasury Manager is using the standard onERC1155Received and onERC1155BatchReceived functions, which handle the receival of ERC1155s:

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


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

Let's say you want to update the onERC1155Received and onERC1155BatchReceived functions to have them emit events when the Treasury Manager receives ERC1155 tokens.

For this, setAdditionalFunction is used. As input, it takes the selector (bytes4), the server (address) and the log (bool) parameters.

function setAdditionalFunction(bytes4 selector, address newServer, bool log) external override authorizedOnly returns (address oldServer);

In this case, the newServer contract contains the new onERC1155Received and onERC1155BatchReceived functions, which emit events:

pragma solidity ^0.8.0;
contract TestContract {
    event SingleTransfer();
    event MultipleTransfer();
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4) {
        emit SingleTransfer();
        return this.onERC1155Received.selector;
    }
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4) {
        emit MultipleTransfer();
        return this.onERC1155BatchReceived.selector;
    }
}

The selectors of the two functions are retrieved:

//onERC1155Received
var selector = web3.utils.sha3("onERC1155Received(address,address,uint256,uint256,bytes)").substring(0, 10) + utilities.voidBytes32.substring(10);

//onERC1155BatchReceived
selector = web3.utils.sha3("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)").substring(0, 10) + utilities.voidBytes32.substring(10);

To be called correctly, the selectors of the new functions must be the same as the original ones. Naturally, only the selectors must be the same; the content of the functions can be different.

Now, the setAdditionalFunction can be called. Every time an ERC1155 token arrives in the Treasury Manager, the SingleTransfer event is emitted (if the tokens are received via the onERC1155Received function) or the MultipleTransfer event is emitted (if the tokens are rececived via the onERC1155BatchReceived function.

Here's an example of the entire integration code, with some useful checks:

pragma solidity ^0.8.0;
contract TestContract {
    event SingleTransfer();
    event MultipleTransfer();
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4) {
        emit SingleTransfer();
        return this.onERC1155Received.selector;
    }
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4) {
        emit MultipleTransfer();
        return this.onERC1155BatchReceived.selector;
    }
}`, 'TestContract');

        var testContract = await new web3.eth.Contract(TestContract.abi).deploy({data : TestContract.bin}).send(blockchainConnection.getSendingOptions());

        //onERC1155Received
        var selector = web3.utils.sha3("onERC1155Received(address,address,uint256,uint256,bytes)").substring(0, 10) + utilities.voidBytes32.substring(10);

        var transaction = await manager.methods.setAdditionalFunction(selector.substring(0, 10), testContract.options.address, true).send(organization.asActiveComponent);
        logs = (await web3.eth.getTransactionReceipt(transaction.transactionHash)).logs;
        logs = logs.filter(it => it.topics[0] === web3.utils.sha3('AdditionalFunction(address,bytes4,address,address)') && it.topics[1] === selector && it.topics[2] === web3.eth.abi.encodeParameter("address", utilities.voidEthereumAddress) && it.topics[3] === web3.eth.abi.encodeParameter("address", testContract.options.address));
        assert.equal(1, logs.length);

        expectedBalance = (await getBalance(manager.options.address, item.options.address, itemId)).add(value);

        transaction = await item.methods.safeTransferFrom(accounts[0], manager.options.address, itemId, value, "0x").send(blockchainConnection.getSendingOptions());
        logs = (await web3.eth.getTransactionReceipt(transaction.transactionHash)).logs;
        logs = logs.filter(it => it.topics[0] === web3.utils.sha3('SingleTransfer()'));
        assert.equal(1, logs.length);

        assert.equal(expectedBalance, (await getBalance(manager.options.address, item.options.address, itemId)));

        
        //onERC1155BatchReceived
        selector = web3.utils.sha3("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)").substring(0, 10) + utilities.voidBytes32.substring(10);

        var transaction = await manager.methods.setAdditionalFunction(selector.substring(0, 10), testContract.options.address, true).send(organization.asActiveComponent);
        logs = (await web3.eth.getTransactionReceipt(transaction.transactionHash)).logs;
        logs = logs.filter(it => it.topics[0] === web3.utils.sha3('AdditionalFunction(address,bytes4,address,address)') && it.topics[1] === selector && it.topics[2] === web3.eth.abi.encodeParameter("address", utilities.voidEthereumAddress) && it.topics[3] === web3.eth.abi.encodeParameter("address", testContract.options.address));
        assert.equal(1, logs.length);

        expectedBalance = (await getBalance(manager.options.address, item.options.address, itemId)).add(value);

        transaction = await item.methods.safeBatchTransferFrom(accounts[0], manager.options.address, [itemId], [value], "0x").send(blockchainConnection.getSendingOptions());
        logs = (await web3.eth.getTransactionReceipt(transaction.transactionHash)).logs;
        logs = logs.filter(it => it.topics[0] === web3.utils.sha3('MultipleTransfer()'));
        assert.equal(1, logs.length);

        assert.equal(expectedBalance, (await getBalance(manager.options.address, item.options.address, itemId)));

Last updated