ETH Price: $2,511.48 (+0.21%)

Transaction Decoder

Block:
18416909 at Oct-24-2023 01:40:47 AM +UTC
Transaction Fee:
0.00641408988068506 ETH $16.11
Gas Used:
214,540 Gas / 29.896941739 Gwei

Emitted Events:

438 Compass-EVM.ValsetUpdated( checkpoint=52F749F2274E4E200FCE4FB02B8A8AA8765B25CF0542263F4FAA28F46B27998F, valset_id=29237 )

Account State Difference:

  Address   Before After State Difference Code
4.453038474852611803 Eth4.453040620252611803 Eth0.0000021454
0x44352095...56a7BcF63
0.04509065233518823 Eth
Nonce: 104
0.03867656245450317 Eth
Nonce: 105
0.00641408988068506
0xa00CF350...52d087157

Execution Trace

Compass-EVM.update_valset( consensus=[{name:valset, type:tuple, order:1, indexed:false, value:[{name:validators, type:address[], order:1, indexed:false, value:[0xa228292447064D5818BbC80b577C91f5212F9355, 0x092604a080524Eff1C875fF13A1b88cBDD41df37, 0xEe21301aF1d9562B5cBEdf520077Ea0a9bC9d535, 0x19f5911E4cA69E30449aD6BB71De341F01F118BB, 0x43E218f96A567DC26C6e65fCabF1fDE26Af69444, 0x1Dd71EE9e7b1CfF10CCb7E12520999c2d4fB45e5, 0x1ad90dB98da083E117D5F62a1673fC0f3A5930ca, 0x6dc59EE4bdFa2C791229004f29b08F783491a934, 0xADe5F5eFeAe72102d998F6C496ea59731a70eEb0, 0x443266026738061972012e62D5Ecd9D98da8B6F4, 0x1F6EF2784cbB2A5011d8CF12356fb5ee3eAeF372, 0x63f55bc560E981d53E1f5bb3643e3a96D26fc635, 0x7C3dE83D90E8d2001a7e3F38E6a7058334cB291E, 0x732fBb6018F2cB5b844055C9EB567447a3132833, 0x5A7C9FC846b18944FD2B8F8aE7255dB5af7b9c04, 0xDEea5b069208e0eE37b630A3e7672FC50e8fE24a, 0xeE83770168D4EE756e74562C17BDED88091519D0, 0x279fC7c0ebEf3328d3899FE73464347A764C5f2C, 0xAAc74d38c82c367b3dA7482E3Aecf6DD7A512eF2, 0x3aB643728bD503AE41561B4Bfe1D309A17255b8D, 0x0DAaBB4FF60423Eb1F14cC6731394e098ad51bcb, 0x4a89f96fdff3C161937CFBC3e22d2E325612aaEc, 0xcFBBF6341cf13a39ffCaF24B86dE09489116783b, 0x14Aa448C2C918C4427c5671028b63BC17f6132d5, 0x377D23948D41579f2c3cA40308e3bdd53f6dA7B2, 0xeB784b37365C302C97C9Ec8cB0933cE344e6dE42, 0xBBEFE691d2C3bbB835CE2958b453ccd05BdB27A5, 0x179206Cd9080A8ee77C2256A27050112da13b2f6], valueString:[0xa228292447064D5818BbC80b577C91f5212F9355, 0x092604a080524Eff1C875fF13A1b88cBDD41df37, 0xEe21301aF1d9562B5cBEdf520077Ea0a9bC9d535, 0x19f5911E4cA69E30449aD6BB71De341F01F118BB, 0x43E218f96A567DC26C6e65fCabF1fDE26Af69444, 0x1Dd71EE9e7b1CfF10CCb7E12520999c2d4fB45e5, 0x1ad90dB98da083E117D5F62a1673fC0f3A5930ca, 0x6dc59EE4bdFa2C791229004f29b08F783491a934, 0xADe5F5eFeAe72102d998F6C496ea59731a70eEb0, 0x443266026738061972012e62D5Ecd9D98da8B6F4, 0x1F6EF2784cbB2A5011d8CF12356fb5ee3eAeF372, 0x63f55bc560E981d53E1f5bb3643e3a96D26fc635, 0x7C3dE83D90E8d2001a7e3F38E6a7058334cB291E, 0x732fBb6018F2cB5b844055C9EB567447a3132833, 0x5A7C9FC846b18944FD2B8F8aE7255dB5af7b9c04, 0xDEea5b069208e0eE37b630A3e7672FC50e8fE24a, 0xeE83770168D4EE756e74562C17BDED88091519D0, 0x279fC7c0ebEf3328d3899FE73464347A764C5f2C, 0xAAc74d38c82c367b3dA7482E3Aecf6DD7A512eF2, 0x3aB643728bD503AE41561B4Bfe1D309A17255b8D, 0x0DAaBB4FF60423Eb1F14cC6731394e098ad51bcb, 0x4a89f96fdff3C161937CFBC3e22d2E325612aaEc, 0xcFBBF6341cf13a39ffCaF24B86dE09489116783b, 0x14Aa448C2C918C4427c5671028b63BC17f6132d5, 0x377D23948D41579f2c3cA40308e3bdd53f6dA7B2, 0xeB784b37365C302C97C9Ec8cB0933cE344e6dE42, 0xBBEFE691d2C3bbB835CE2958b453ccd05BdB27A5, 0x179206Cd9080A8ee77C2256A27050112da13b2f6]}, {name:powers, type:uint256[], order:2, indexed:false, value:[377084175, 235314280, 232682876, 194308259, 191145421, 180769369, 178304352, 178013853, 177130363, 176483556, 175800080, 172437759, 170517867, 168058762, 167619087, 146578773, 145745962, 140009707, 135215150, 129283106, 126192034, 126182317, 122927772, 119641898, 71875217, 54456773, 1005064, 183449], valueString:[377084175, 235314280, 232682876, 194308259, 191145421, 180769369, 178304352, 178013853, 177130363, 176483556, 175800080, 172437759, 170517867, 168058762, 167619087, 146578773, 145745962, 140009707, 135215150, 129283106, 126192034, 126182317, 122927772, 119641898, 71875217, 54456773, 1005064, 183449]}, {name:valset_id, type:uint256, order:3, indexed:false, value:29236, valueString:29236}], valueString:[{name:validators, type:address[], order:1, indexed:false, value:[0xa228292447064D5818BbC80b577C91f5212F9355, 0x092604a080524Eff1C875fF13A1b88cBDD41df37, 0xEe21301aF1d9562B5cBEdf520077Ea0a9bC9d535, 0x19f5911E4cA69E30449aD6BB71De341F01F118BB, 0x43E218f96A567DC26C6e65fCabF1fDE26Af69444, 0x1Dd71EE9e7b1CfF10CCb7E12520999c2d4fB45e5, 0x1ad90dB98da083E117D5F62a1673fC0f3A5930ca, 0x6dc59EE4bdFa2C791229004f29b08F783491a934, 0xADe5F5eFeAe72102d998F6C496ea59731a70eEb0, 0x443266026738061972012e62D5Ecd9D98da8B6F4, 0x1F6EF2784cbB2A5011d8CF12356fb5ee3eAeF372, 0x63f55bc560E981d53E1f5bb3643e3a96D26fc635, 0x7C3dE83D90E8d2001a7e3F38E6a7058334cB291E, 0x732fBb6018F2cB5b844055C9EB567447a3132833, 0x5A7C9FC846b18944FD2B8F8aE7255dB5af7b9c04, 0xDEea5b069208e0eE37b630A3e7672FC50e8fE24a, 0xeE83770168D4EE756e74562C17BDED88091519D0, 0x279fC7c0ebEf3328d3899FE73464347A764C5f2C, 0xAAc74d38c82c367b3dA7482E3Aecf6DD7A512eF2, 0x3aB643728bD503AE41561B4Bfe1D309A17255b8D, 0x0DAaBB4FF60423Eb1F14cC6731394e098ad51bcb, 0x4a89f96fdff3C161937CFBC3e22d2E325612aaEc, 0xcFBBF6341cf13a39ffCaF24B86dE09489116783b, 0x14Aa448C2C918C4427c5671028b63BC17f6132d5, 0x377D23948D41579f2c3cA40308e3bdd53f6dA7B2, 0xeB784b37365C302C97C9Ec8cB0933cE344e6dE42, 0xBBEFE691d2C3bbB835CE2958b453ccd05BdB27A5, 0x179206Cd9080A8ee77C2256A27050112da13b2f6], valueString:[0xa228292447064D5818BbC80b577C91f5212F9355, 0x092604a080524Eff1C875fF13A1b88cBDD41df37, 0xEe21301aF1d9562B5cBEdf520077Ea0a9bC9d535, 0x19f5911E4cA69E30449aD6BB71De341F01F118BB, 0x43E218f96A567DC26C6e65fCabF1fDE26Af69444, 0x1Dd71EE9e7b1CfF10CCb7E12520999c2d4fB45e5, 0x1ad90dB98da083E117D5F62a1673fC0f3A5930ca, 0x6dc59EE4bdFa2C791229004f29b08F783491a934, 0xADe5F5eFeAe72102d998F6C496ea59731a70eEb0, 0x443266026738061972012e62D5Ecd9D98da8B6F4, 0x1F6EF2784cbB2A5011d8CF12356fb5ee3eAeF372, 0x63f55bc560E981d53E1f5bb3643e3a96D26fc635, 0x7C3dE83D90E8d2001a7e3F38E6a7058334cB291E, 0x732fBb6018F2cB5b844055C9EB567447a3132833, 0x5A7C9FC846b18944FD2B8F8aE7255dB5af7b9c04, 0xDEea5b069208e0eE37b630A3e7672FC50e8fE24a, 0xeE83770168D4EE756e74562C17BDED88091519D0, 0x279fC7c0ebEf3328d3899FE73464347A764C5f2C, 0xAAc74d38c82c367b3dA7482E3Aecf6DD7A512eF2, 0x3aB643728bD503AE41561B4Bfe1D309A17255b8D, 0x0DAaBB4FF60423Eb1F14cC6731394e098ad51bcb, 0x4a89f96fdff3C161937CFBC3e22d2E325612aaEc, 0xcFBBF6341cf13a39ffCaF24B86dE09489116783b, 0x14Aa448C2C918C4427c5671028b63BC17f6132d5, 0x377D23948D41579f2c3cA40308e3bdd53f6dA7B2, 0xeB784b37365C302C97C9Ec8cB0933cE344e6dE42, 0xBBEFE691d2C3bbB835CE2958b453ccd05BdB27A5, 0x179206Cd9080A8ee77C2256A27050112da13b2f6]}, {name:powers, type:uint256[], order:2, indexed:false, value:[377084175, 235314280, 232682876, 194308259, 191145421, 180769369, 178304352, 178013853, 177130363, 176483556, 175800080, 172437759, 170517867, 168058762, 167619087, 146578773, 145745962, 140009707, 135215150, 129283106, 126192034, 126182317, 122927772, 119641898, 71875217, 54456773, 1005064, 183449], valueString:[377084175, 235314280, 232682876, 194308259, 191145421, 180769369, 178304352, 178013853, 177130363, 176483556, 175800080, 172437759, 170517867, 168058762, 167619087, 146578773, 145745962, 140009707, 135215150, 129283106, 126192034, 126182317, 122927772, 119641898, 71875217, 54456773, 1005064, 183449]}, {name:valset_id, type:uint256, order:3, indexed:false, value:29236, valueString:29236}]}, {name:signatures, type:tuple[], order:2, indexed:false}], new_valset=[{name:validators, type:address[], order:1, indexed:false, value:[0xa228292447064D5818BbC80b577C91f5212F9355, 0x092604a080524Eff1C875fF13A1b88cBDD41df37, 0xEe21301aF1d9562B5cBEdf520077Ea0a9bC9d535, 0x19f5911E4cA69E30449aD6BB71De341F01F118BB, 0x43E218f96A567DC26C6e65fCabF1fDE26Af69444, 0x1Dd71EE9e7b1CfF10CCb7E12520999c2d4fB45e5, 0x1ad90dB98da083E117D5F62a1673fC0f3A5930ca, 0x6dc59EE4bdFa2C791229004f29b08F783491a934, 0xADe5F5eFeAe72102d998F6C496ea59731a70eEb0, 0x443266026738061972012e62D5Ecd9D98da8B6F4, 0x1F6EF2784cbB2A5011d8CF12356fb5ee3eAeF372, 0x63f55bc560E981d53E1f5bb3643e3a96D26fc635, 0x7C3dE83D90E8d2001a7e3F38E6a7058334cB291E, 0x732fBb6018F2cB5b844055C9EB567447a3132833, 0x5A7C9FC846b18944FD2B8F8aE7255dB5af7b9c04, 0xDEea5b069208e0eE37b630A3e7672FC50e8fE24a, 0xeE83770168D4EE756e74562C17BDED88091519D0, 0x279fC7c0ebEf3328d3899FE73464347A764C5f2C, 0xAAc74d38c82c367b3dA7482E3Aecf6DD7A512eF2, 0x3aB643728bD503AE41561B4Bfe1D309A17255b8D, 0x0DAaBB4FF60423Eb1F14cC6731394e098ad51bcb, 0x4a89f96fdff3C161937CFBC3e22d2E325612aaEc, 0xcFBBF6341cf13a39ffCaF24B86dE09489116783b, 0x14Aa448C2C918C4427c5671028b63BC17f6132d5, 0x443520951d766456F2AC5578f3E7e4B56a7BcF63, 0x377D23948D41579f2c3cA40308e3bdd53f6dA7B2, 0xeB784b37365C302C97C9Ec8cB0933cE344e6dE42, 0xBBEFE691d2C3bbB835CE2958b453ccd05BdB27A5, 0x179206Cd9080A8ee77C2256A27050112da13b2f6], valueString:[0xa228292447064D5818BbC80b577C91f5212F9355, 0x092604a080524Eff1C875fF13A1b88cBDD41df37, 0xEe21301aF1d9562B5cBEdf520077Ea0a9bC9d535, 0x19f5911E4cA69E30449aD6BB71De341F01F118BB, 0x43E218f96A567DC26C6e65fCabF1fDE26Af69444, 0x1Dd71EE9e7b1CfF10CCb7E12520999c2d4fB45e5, 0x1ad90dB98da083E117D5F62a1673fC0f3A5930ca, 0x6dc59EE4bdFa2C791229004f29b08F783491a934, 0xADe5F5eFeAe72102d998F6C496ea59731a70eEb0, 0x443266026738061972012e62D5Ecd9D98da8B6F4, 0x1F6EF2784cbB2A5011d8CF12356fb5ee3eAeF372, 0x63f55bc560E981d53E1f5bb3643e3a96D26fc635, 0x7C3dE83D90E8d2001a7e3F38E6a7058334cB291E, 0x732fBb6018F2cB5b844055C9EB567447a3132833, 0x5A7C9FC846b18944FD2B8F8aE7255dB5af7b9c04, 0xDEea5b069208e0eE37b630A3e7672FC50e8fE24a, 0xeE83770168D4EE756e74562C17BDED88091519D0, 0x279fC7c0ebEf3328d3899FE73464347A764C5f2C, 0xAAc74d38c82c367b3dA7482E3Aecf6DD7A512eF2, 0x3aB643728bD503AE41561B4Bfe1D309A17255b8D, 0x0DAaBB4FF60423Eb1F14cC6731394e098ad51bcb, 0x4a89f96fdff3C161937CFBC3e22d2E325612aaEc, 0xcFBBF6341cf13a39ffCaF24B86dE09489116783b, 0x14Aa448C2C918C4427c5671028b63BC17f6132d5, 0x443520951d766456F2AC5578f3E7e4B56a7BcF63, 0x377D23948D41579f2c3cA40308e3bdd53f6dA7B2, 0xeB784b37365C302C97C9Ec8cB0933cE344e6dE42, 0xBBEFE691d2C3bbB835CE2958b453ccd05BdB27A5, 0x179206Cd9080A8ee77C2256A27050112da13b2f6]}, {name:powers, type:uint256[], order:2, indexed:false, value:[367390857, 229265296, 226701535, 189313375, 186231840, 176122515, 173720864, 173437833, 172577054, 171946873, 171280966, 168005078, 166134538, 163738647, 163310274, 142810822, 141999419, 136410620, 131739313, 125964543, 122948145, 122938677, 119767793, 116566385, 110401553, 70027594, 53056909, 979228, 178734], valueString:[367390857, 229265296, 226701535, 189313375, 186231840, 176122515, 173720864, 173437833, 172577054, 171946873, 171280966, 168005078, 166134538, 163738647, 163310274, 142810822, 141999419, 136410620, 131739313, 125964543, 122948145, 122938677, 119767793, 116566385, 110401553, 70027594, 53056909, 979228, 178734]}, {name:valset_id, type:uint256, order:3, indexed:false, value:29237, valueString:29237}] )
  • Null: 0x000...004.00000000( )
  • Null: 0x000...004.00000000( )
  • Null: 0x000...004.00000000( )
  • Null: 0x000...004.00000000( )
  • Null: 0x000...004.00000000( )
  • Null: 0x000...004.00000000( )
  • Null: 0x000...004.00000000( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
  • Null: 0x000...001.ab09a30e( )
    # @version 0.3.7
    """
    @title Compass-EVM
    @author Volume.Finance
    """
    
    MAX_VALIDATORS: constant(uint256) = 320
    MAX_PAYLOAD: constant(uint256) = 20480
    MAX_BATCH: constant(uint256) = 64
    
    POWER_THRESHOLD: constant(uint256) = 2_863_311_530 # 2/3 of 2^32, Validator powers will be normalized to sum to 2 ^ 32 in every valset update.
    COMPASS_ID: immutable(bytes32)
    
    interface ERC20:
        def balanceOf(_owner: address) -> uint256: view
    
    struct Valset:
        validators: DynArray[address, MAX_VALIDATORS] # Validator addresses
        powers: DynArray[uint256, MAX_VALIDATORS] # Powers of given validators, in the same order as validators array
        valset_id: uint256 # nonce of this validator set
    
    struct Signature:
        v: uint256
        r: uint256
        s: uint256
    
    struct Consensus:
        valset: Valset # Valset data
        signatures: DynArray[Signature, MAX_VALIDATORS] # signatures in the same order as validator array in valset
    
    struct LogicCallArgs:
        logic_contract_address: address # the arbitrary contract address to external call
        payload: Bytes[MAX_PAYLOAD] # payloads
    
    struct TokenSendArgs:
        receiver: DynArray[address, MAX_BATCH]
        amount: DynArray[uint256, MAX_BATCH]
    
    event ValsetUpdated:
        checkpoint: bytes32
        valset_id: uint256
    
    event LogicCallEvent:
        logic_contract_address: address
        payload: Bytes[MAX_PAYLOAD]
        message_id: uint256
    
    event SendToPalomaEvent:
        token: address
        sender: address
        receiver: String[64]
        amount: uint256
    
    event BatchSendEvent:
        token: address
        message_id: uint256
    
    event ERC20DeployedEvent:
        paloma_denom: String[64]
        token_contract: address
        name: String[64]
        symbol: String[32]
        decimals: uint8
    
    last_checkpoint: public(bytes32)
    last_valset_id: public(uint256)
    message_id_used: public(HashMap[uint256, bool])
    
    # compass_id: unique identifier for compass instance
    # valset: initial validator set
    @external
    def __init__(compass_id: bytes32, valset: Valset):
        COMPASS_ID = compass_id
        cumulative_power: uint256 = 0
        i: uint256 = 0
        # check cumulative power is enough
        for validator in valset.validators:
            cumulative_power += valset.powers[i]
            if cumulative_power >= POWER_THRESHOLD:
                break
            i += 1
        assert cumulative_power >= POWER_THRESHOLD, "Insufficient Power"
        new_checkpoint: bytes32 = keccak256(_abi_encode(valset.validators, valset.powers, valset.valset_id, compass_id, method_id=method_id("checkpoint(address[],uint256[],uint256,bytes32)")))
        self.last_checkpoint = new_checkpoint
        self.last_valset_id = valset.valset_id
        log ValsetUpdated(new_checkpoint, valset.valset_id)
    
    @external
    @pure
    def compass_id() -> bytes32:
        return COMPASS_ID
    
    # utility function to verify EIP712 signature
    @internal
    @pure
    def verify_signature(signer: address, hash: bytes32, sig: Signature) -> bool:
        message_digest: bytes32 = keccak256(concat(convert("\x19Ethereum Signed Message:\n32", Bytes[28]), hash))
        return signer == ecrecover(message_digest, sig.v, sig.r, sig.s)
    
    # consensus: validator set and signatures
    # hash: what we are checking they have signed
    @internal
    def check_validator_signatures(consensus: Consensus, hash: bytes32):
        i: uint256 = 0
        cumulative_power: uint256 = 0
        for sig in consensus.signatures:
            if sig.v != 0:
                assert self.verify_signature(consensus.valset.validators[i], hash, sig), "Invalid Signature"
                cumulative_power += consensus.valset.powers[i]
                if cumulative_power >= POWER_THRESHOLD:
                    break
            i += 1
        assert cumulative_power >= POWER_THRESHOLD, "Insufficient Power"
    
    # Make a new checkpoint from the supplied validator set
    # A checkpoint is a hash of all relevant information about the valset. This is stored by the contract,
    # instead of storing the information directly. This saves on storage and gas.
    # The format of the checkpoint is:
    # keccak256 hash of abi_encoded checkpoint(validators[], powers[], valset_id, compass_id)
    # The validator powers must be decreasing or equal. This is important for checking the signatures on the
    # next valset, since it allows the caller to stop verifying signatures once a quorum of signatures have been verified.
    @internal
    @view
    def make_checkpoint(valset: Valset) -> bytes32:
        return keccak256(_abi_encode(valset.validators, valset.powers, valset.valset_id, COMPASS_ID, method_id=method_id("checkpoint(address[],uint256[],uint256,bytes32)")))
    
    # This updates the valset by checking that the validators in the current valset have signed off on the
    # new valset. The signatures supplied are the signatures of the current valset over the checkpoint hash
    # generated from the new valset.
    # Anyone can call this function, but they must supply valid signatures of constant_powerThreshold of the current valset over
    # the new valset.
    # valset: new validator set to update with
    # consensus: current validator set and signatures
    @external
    def update_valset(consensus: Consensus, new_valset: Valset):
        # check if new valset_id is greater than current valset_id
        assert new_valset.valset_id > consensus.valset.valset_id, "Invalid Valset ID"
        cumulative_power: uint256 = 0
        i: uint256 = 0
        # check cumulative power is enough
        for validator in new_valset.validators:
            cumulative_power += new_valset.powers[i]
            if cumulative_power >= POWER_THRESHOLD:
                break
            i += 1
        assert cumulative_power >= POWER_THRESHOLD, "Insufficient Power"
        # check if the supplied current validator set matches the saved checkpoint
        assert self.last_checkpoint == self.make_checkpoint(consensus.valset), "Incorrect Checkpoint"
        # calculate the new checkpoint
        new_checkpoint: bytes32 = self.make_checkpoint(new_valset)
        # check if enough validators signed new validator set (new checkpoint)
        self.check_validator_signatures(consensus, new_checkpoint)
        self.last_checkpoint = new_checkpoint
        self.last_valset_id = new_valset.valset_id
        log ValsetUpdated(new_checkpoint, new_valset.valset_id)
    
    # This makes calls to contracts that execute arbitrary logic
    # message_id is to prevent replay attack and every message_id can be used only once
    @external
    def submit_logic_call(consensus: Consensus, args: LogicCallArgs, message_id: uint256, deadline: uint256):
        assert block.timestamp <= deadline, "Timeout"
        assert not self.message_id_used[message_id], "Used Message_ID"
        self.message_id_used[message_id] = True
        # check if the supplied current validator set matches the saved checkpoint
        assert self.last_checkpoint == self.make_checkpoint(consensus.valset), "Incorrect Checkpoint"
        # signing data is keccak256 hash of abi_encoded logic_call(args, message_id, compass_id, deadline)
        args_hash: bytes32 = keccak256(_abi_encode(args, message_id, COMPASS_ID, deadline, method_id=method_id("logic_call((address,bytes),uint256,bytes32,uint256)")))
        # check if enough validators signed args_hash
        self.check_validator_signatures(consensus, args_hash)
        # make call to logic contract
        raw_call(args.logic_contract_address, args.payload)
        log LogicCallEvent(args.logic_contract_address, args.payload, message_id)
    
    @internal
    def _safe_transfer_from(_token: address, _from: address, _to: address, _value: uint256):
        _response: Bytes[32] = raw_call(
            _token,
            _abi_encode(
                _from, _to, _value,
                method_id=method_id("transferFrom(address,address,uint256)")),
            max_outsize=32
        )  # dev: failed transferFrom
        if len(_response) > 0:
            assert convert(_response, bool), "TransferFrom failed"
    
    @external
    def send_token_to_paloma(token: address, receiver: String[64], amount: uint256):
        _balance: uint256 = ERC20(token).balanceOf(self)
        self._safe_transfer_from(token, msg.sender, self, amount)
        _balance = ERC20(token).balanceOf(self) - _balance
        assert _balance > 0, "Zero Transfer"
        log SendToPalomaEvent(token, msg.sender, receiver, amount)
    
    @internal
    def _safe_transfer(_token: address, _to: address, _value: uint256):
        _response: Bytes[32] = raw_call(
            _token,
            _abi_encode(
                _to, _value,
                method_id=method_id("transfer(address,uint256)")),
            max_outsize=32
        )  # dev: failed transferFrom
        if len(_response) > 0:
            assert convert(_response, bool), "TransferFrom failed"
    
    @external
    def submit_batch(consensus: Consensus, token: address, args: TokenSendArgs, message_id: uint256, deadline: uint256):
        assert block.timestamp <= deadline, "Timeout"
        assert not self.message_id_used[message_id], "Used Message_ID"
        length: uint256 = len(args.receiver)
        assert length == len(args.amount), "Unmatched Params"
        self.message_id_used[message_id] = True
        # check if the supplied current validator set matches the saved checkpoint
        assert self.last_checkpoint == self.make_checkpoint(consensus.valset), "Incorrect Checkpoint"
        # signing data is keccak256 hash of abi_encoded logic_call(args, message_id, compass_id, deadline)
        args_hash: bytes32 = keccak256(_abi_encode(token, args, message_id, COMPASS_ID, deadline, method_id=method_id("batch_call(address,(address[],uint256[]),uint256,bytes32,uint256)")))
        # check if enough validators signed args_hash
        self.check_validator_signatures(consensus, args_hash)
        # make call to logic contract
        for i in range(MAX_BATCH):
            if  i >= length:
                break
            self._safe_transfer(token, args.receiver[i], args.amount[i])
        log BatchSendEvent(token, message_id)
    
    @external
    def deploy_erc20(_paloma_denom: String[64], _name: String[64], _symbol: String[32], _decimals: uint8, _blueprint: address):
        assert msg.sender == self, "Invalid"
        erc20: address = create_from_blueprint(_blueprint, self, _name, _symbol, _decimals, code_offset=3)
        log ERC20DeployedEvent(_paloma_denom, erc20, _name, _symbol, _decimals)