Onchain data verifcation

Now that the data has been submitted from Github Automation, the DAO verifies it and executes the data

As described before, the data gets submitted to the Vote Executor on Ethereum mainnet. You may be concerned that Github Actions is centralised and therefore puts the system at the hands of Github. However, this completely untrue.

How is this decentralised?

Even though we automated the process using Github Actions, the vote execution system remains decentralised because ANYONE can submit data to be executed on chain.

/// @notice Allows anyone to submit data for execution of votes
/// @dev Attempts to parse at high level and then confirm hash before submitting to queue
/// @param data Payload fully encoded as required (see formatting using encoding functions below)

function submitData(bytes memory data) external {

    (bytes32 hashed, Message[] memory _messages) = abi.decode(data, (bytes32, Message[]));

    require(hashed == keccak256(abi.encode(_messages)), "Hash doesn't match");

    SubmittedData memory newSubmittedData;
    newSubmittedData.data = data;
    newSubmittedData.time = block.timestamp;

By passing in data that is parsed through encoding messages using the helper view functions provided, anyone can submit data.

How does the DAO verify submitted data?

When someone submits data to be executed, they receive a hash of the data.

The DAO confirms that the data indeed reflects the outcome of the votes and only then signs this hash and submits it to the Vote Executor on chain.

/// @notice Allow anyone to approve data for execution given off-chain signatures
/// @dev Checks against existing sigs submitted and only allow non-duplicate multisig owner signatures to approve the payload
/// @param _dataId Id of data payload to be approved
/// @param _signs Array of off-chain EOA signatures to approve the payload.

function approveSubmittedData(uint256 _dataId, bytes[] memory _signs) external {
    (bytes32 dataHash,) = abi.decode(submittedData[_dataId].data, (bytes32, Message[]));

    address[] memory owners = IGnosis(gnosis).getOwners();

    bytes[] memory submittedSigns = submittedData[_dataId].signs;
    address[] memory uniqueSigners = new address[](owners.length);
    uint256 numberOfSigns;

    for (uint256 i; i< submittedSigns.length; i++) {
        uniqueSigners[i]= _getSignerAddress(dataHash, submittedSigns[i]);

    for (uint256 i; i < _signs.length; i++) {
        for (uint256 j; j < owners.length; j++) {
            if(_verify(dataHash, _signs[i], owners[j]) && _checkUniqueSignature(uniqueSigners, owners[j])){
                uniqueSigners[numberOfSigns] = owners[j];

Simply, the contract checks if a member of the multisig of the DAO has signed the hash and - if it is valid - it approves the data. The data requires 2 multisig signers to approve it before it can be executed using executeSpecificHash():

function executeSpecificData(uint256 index) external {
        (bytes32 hashed, Message[] memory messages) = abi.decode(submittedData[index].data, (bytes32, Message[]));
        require(submittedData[index].time + timeLock < block.timestamp, "Under timelock");
        require(hashExecutionTime[hashed] == 0, "Duplicate Hash");

        if(submittedData[index].signs.length >= minSigns){
            for (uint256 j; j < messages.length; j++) {
                if(messages[j].commandIndex == 0){
                    (string memory ibAlluoSymbol, uint256 newAnnualInterest, uint256 newInterestPerSecond) = abi.decode(messages[j].commandData, (string, uint256, uint256));
                    IIbAlluo ibAlluo = IIbAlluo(ibAlluoSymbolToAddress[ibAlluoSymbol]);
                    if(ibAlluo.annualInterest() != newAnnualInterest){
                       ibAlluo.setInterest(newAnnualInterest, newInterestPerSecond);
                else if(messages[j].commandIndex == 1){
                    (uint256 mintAmount, uint256 period) = abi.decode(messages[j].commandData, (uint256, uint256));
                    IAlluoToken(ALLUO).mint(locker, mintAmount);
                    ILocker(locker).setReward(mintAmount / period);
            hashExecutionTime[hashed] = block.timestamp;
            bytes memory finalData = abi.encode(submittedData[index].data, submittedData[index].signs);
            IAnyCall(bridgingInfo.anyCallAddress).anyCall(bridgingInfo.nextChainExecutor, finalData, address(0), bridgingInfo.nextChain, 0);

Last updated