add amb base implementation on oracle

This commit is contained in:
Gerardo Nardelli 2019-05-24 15:42:26 -03:00
parent 94e923d965
commit 7ca3b0079c
17 changed files with 2447 additions and 19 deletions

@ -0,0 +1,682 @@
[
{
"constant": false,
"inputs": [
{
"name": "_contract",
"type": "address"
},
{
"name": "_data",
"type": "bytes"
},
{
"name": "_gas",
"type": "uint256"
},
{
"name": "_oracleGasPriceSpeed",
"type": "bytes1"
}
],
"name": "requireToPassMessage",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_contract",
"type": "address"
}
],
"name": "depositForContractSender",
"outputs": [],
"payable": true,
"stateMutability": "payable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_txHash",
"type": "bytes32"
}
],
"name": "relayedMessages",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_day",
"type": "uint256"
}
],
"name": "totalSpentPerDay",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "isInitialized",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "setDefrayalModeForHomeToForeign",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getCurrentDay",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "requiredBlockConfirmations",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_data",
"type": "bytes"
}
],
"name": "getMinimumGasUsage",
"outputs": [
{
"name": "gas",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getBridgeMode",
"outputs": [
{
"name": "_data",
"type": "bytes4"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "setSubsidizedModeForForeignToHome",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_validatorContract",
"type": "address"
},
{
"name": "_maxGasPerTx",
"type": "uint256"
},
{
"name": "_gasPrice",
"type": "uint256"
},
{
"name": "_requiredBlockConfirmations",
"type": "uint256"
}
],
"name": "initialize",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_data",
"type": "bytes"
},
{
"name": "vs",
"type": "uint8[]"
},
{
"name": "rs",
"type": "bytes32[]"
},
{
"name": "ss",
"type": "bytes32[]"
}
],
"name": "executeSignatures",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "dailyLimit",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_token",
"type": "address"
},
{
"name": "_to",
"type": "address"
}
],
"name": "claimTokens",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "homeToForeignMode",
"outputs": [
{
"name": "",
"type": "bytes"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_balanceHolder",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_maxGasPerTx",
"type": "uint256"
}
],
"name": "setMaxGasPerTx",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_recipient",
"type": "address"
}
],
"name": "withdrawFromDeposit",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "setSubsidizedModeForHomeToForeign",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "requiredSignatures",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "foreignToHomeMode",
"outputs": [
{
"name": "",
"type": "bytes"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "setDefrayalModeForForeignToHome",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "validatorContract",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "deployedAtBlock",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getBridgeInterfacesVersion",
"outputs": [
{
"name": "major",
"type": "uint64"
},
{
"name": "minor",
"type": "uint64"
},
{
"name": "patch",
"type": "uint64"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_minPerTx",
"type": "uint256"
}
],
"name": "setMinPerTx",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_blockConfirmations",
"type": "uint256"
}
],
"name": "setRequiredBlockConfirmations",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_dailyLimit",
"type": "uint256"
}
],
"name": "setDailyLimit",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_gasPrice",
"type": "uint256"
}
],
"name": "setGasPrice",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_maxPerTx",
"type": "uint256"
}
],
"name": "setMaxPerTx",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_contract",
"type": "address"
},
{
"name": "_data",
"type": "bytes"
},
{
"name": "_gas",
"type": "uint256"
}
],
"name": "requireToPassMessage",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "minPerTx",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "maxGasPerTx",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_contract",
"type": "address"
},
{
"name": "_data",
"type": "bytes"
},
{
"name": "_gas",
"type": "uint256"
},
{
"name": "_gasPrice",
"type": "uint256"
}
],
"name": "requireToPassMessage",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_amount",
"type": "uint256"
}
],
"name": "withinLimit",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "maxPerTx",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "gasPrice",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "encodedData",
"type": "bytes"
}
],
"name": "UserRequestForAffirmation",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "sender",
"type": "address"
},
{
"indexed": false,
"name": "executor",
"type": "address"
},
{
"indexed": false,
"name": "transactionHash",
"type": "bytes32"
}
],
"name": "RelayedMessage",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "gasPrice",
"type": "uint256"
}
],
"name": "GasPriceChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "requiredBlockConfirmations",
"type": "uint256"
}
],
"name": "RequiredBlockConfirmationChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newLimit",
"type": "uint256"
}
],
"name": "DailyLimitChanged",
"type": "event"
}
]

@ -0,0 +1,862 @@
[
{
"constant": true,
"inputs": [
{
"name": "_message",
"type": "bytes32"
}
],
"name": "numMessagesSigned",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_contract",
"type": "address"
},
{
"name": "_data",
"type": "bytes"
},
{
"name": "_gas",
"type": "uint256"
},
{
"name": "_oracleGasPriceSpeed",
"type": "bytes1"
}
],
"name": "requireToPassMessage",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_contract",
"type": "address"
}
],
"name": "depositForContractSender",
"outputs": [],
"payable": true,
"stateMutability": "payable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_hash",
"type": "bytes32"
},
{
"name": "_index",
"type": "uint256"
}
],
"name": "signature",
"outputs": [
{
"name": "",
"type": "bytes"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_day",
"type": "uint256"
}
],
"name": "totalSpentPerDay",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "isInitialized",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "setDefrayalModeForHomeToForeign",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getCurrentDay",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "requiredBlockConfirmations",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_data",
"type": "bytes"
}
],
"name": "getMinimumGasUsage",
"outputs": [
{
"name": "gas",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getBridgeMode",
"outputs": [
{
"name": "_data",
"type": "bytes4"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_hash",
"type": "bytes32"
}
],
"name": "message",
"outputs": [
{
"name": "",
"type": "bytes"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "setSubsidizedModeForForeignToHome",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_validatorContract",
"type": "address"
},
{
"name": "_maxGasPerTx",
"type": "uint256"
},
{
"name": "_gasPrice",
"type": "uint256"
},
{
"name": "_requiredBlockConfirmations",
"type": "uint256"
}
],
"name": "initialize",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "signature",
"type": "bytes"
},
{
"name": "message",
"type": "bytes"
}
],
"name": "submitSignature",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "dailyLimit",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_token",
"type": "address"
},
{
"name": "_to",
"type": "address"
}
],
"name": "claimTokens",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_hash",
"type": "bytes32"
}
],
"name": "numAffirmationsSigned",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "homeToForeignMode",
"outputs": [
{
"name": "",
"type": "bytes"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_balanceHolder",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_hash",
"type": "bytes32"
}
],
"name": "affirmationsSigned",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_maxGasPerTx",
"type": "uint256"
}
],
"name": "setMaxGasPerTx",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_recipient",
"type": "address"
}
],
"name": "withdrawFromDeposit",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "setSubsidizedModeForHomeToForeign",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "requiredSignatures",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_message",
"type": "bytes32"
}
],
"name": "messagesSigned",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "foreignToHomeMode",
"outputs": [
{
"name": "",
"type": "bytes"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "setDefrayalModeForForeignToHome",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "validatorContract",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "deployedAtBlock",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getBridgeInterfacesVersion",
"outputs": [
{
"name": "major",
"type": "uint64"
},
{
"name": "minor",
"type": "uint64"
},
{
"name": "patch",
"type": "uint64"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_minPerTx",
"type": "uint256"
}
],
"name": "setMinPerTx",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_blockConfirmations",
"type": "uint256"
}
],
"name": "setRequiredBlockConfirmations",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_dailyLimit",
"type": "uint256"
}
],
"name": "setDailyLimit",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_gasPrice",
"type": "uint256"
}
],
"name": "setGasPrice",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_maxPerTx",
"type": "uint256"
}
],
"name": "setMaxPerTx",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_contract",
"type": "address"
},
{
"name": "_data",
"type": "bytes"
},
{
"name": "_gas",
"type": "uint256"
}
],
"name": "requireToPassMessage",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "minPerTx",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "maxGasPerTx",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "message",
"type": "bytes"
}
],
"name": "executeAffirmation",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_contract",
"type": "address"
},
{
"name": "_data",
"type": "bytes"
},
{
"name": "_gas",
"type": "uint256"
},
{
"name": "_gasPrice",
"type": "uint256"
}
],
"name": "requireToPassMessage",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_amount",
"type": "uint256"
}
],
"name": "withinLimit",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "maxPerTx",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "gasPrice",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_number",
"type": "uint256"
}
],
"name": "isAlreadyProcessed",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "encodedData",
"type": "bytes"
}
],
"name": "UserRequestForSignature",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "sender",
"type": "address"
},
{
"indexed": false,
"name": "executor",
"type": "address"
},
{
"indexed": false,
"name": "txHash",
"type": "bytes32"
}
],
"name": "AffirmationCompleted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "signer",
"type": "address"
},
{
"indexed": false,
"name": "messageHash",
"type": "bytes32"
}
],
"name": "SignedForUserRequest",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "signer",
"type": "address"
},
{
"indexed": false,
"name": "messageHash",
"type": "bytes32"
}
],
"name": "SignedForAffirmation",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "authorityResponsibleForRelay",
"type": "address"
},
{
"indexed": false,
"name": "messageHash",
"type": "bytes32"
},
{
"indexed": false,
"name": "NumberOfCollectedSignatures",
"type": "uint256"
}
],
"name": "CollectedSignatures",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "gasPrice",
"type": "uint256"
}
],
"name": "GasPriceChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "requiredBlockConfirmations",
"type": "uint256"
}
],
"name": "RequiredBlockConfirmationChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newLimit",
"type": "uint256"
}
],
"name": "DailyLimitChanged",
"type": "event"
}
]

@ -13,6 +13,9 @@ const foreignErc677Erc677Abi = require('../abis/ForeignBridgeErc677ToErc677.abi'
const homeErcNativeAbi = require('../abis/HomeBridgeErcToNative.abi') const homeErcNativeAbi = require('../abis/HomeBridgeErcToNative.abi')
const foreignErcNativeAbi = require('../abis/ForeignBridgeErcToNative.abi') const foreignErcNativeAbi = require('../abis/ForeignBridgeErcToNative.abi')
const homeAMBAbi = require('../abis/HomeAMB.abi')
const foreignAMBAbi = require('../abis/ForeignAMB.abi')
const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env const { VALIDATOR_ADDRESS, VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env
let homeAbi let homeAbi
@ -35,6 +38,11 @@ switch (process.env.BRIDGE_MODE) {
foreignAbi = foreignErcNativeAbi foreignAbi = foreignErcNativeAbi
id = 'erc-native' id = 'erc-native'
break break
case 'ARBITRARY_MESSAGE':
homeAbi = homeAMBAbi
foreignAbi = foreignAMBAbi
id = 'amb'
break
default: default:
if (process.env.NODE_ENV !== 'test') { if (process.env.NODE_ENV !== 'test') {
throw new Error(`Bridge Mode: ${process.env.BRIDGE_MODE} not supported.`) throw new Error(`Bridge Mode: ${process.env.BRIDGE_MODE} not supported.`)

@ -0,0 +1,57 @@
const { HttpListProviderError } = require('http-list-provider')
const {
AlreadyProcessedError,
AlreadySignedError,
InvalidValidatorError
} = require('../../utils/errors')
const logger = require('../../services/logger').child({
module: 'processAffirmationRequests:estimateGas'
})
async function estimateGas({ web3, homeBridge, validatorContract, message, address }) {
try {
const gasEstimate = await homeBridge.methods.executeAffirmation(message).estimateGas({
from: address
})
return gasEstimate
} catch (e) {
if (e instanceof HttpListProviderError) {
throw e
}
const messageHash = web3.utils.soliditySha3(message)
const senderHash = web3.utils.soliditySha3(address, messageHash)
// Check if minimum number of validations was already reached
logger.debug('Check if minimum number of validations was already reached')
const numAffirmationsSigned = await homeBridge.methods.numAffirmationsSigned(messageHash).call()
const alreadyProcessed = await homeBridge.methods
.isAlreadyProcessed(numAffirmationsSigned)
.call()
if (alreadyProcessed) {
throw new AlreadyProcessedError(e.message)
}
// Check if the message was already signed by this validator
logger.debug('Check if the message was already signed')
const alreadySigned = await homeBridge.methods.affirmationsSigned(senderHash).call()
if (alreadySigned) {
throw new AlreadySignedError(e.message)
}
// Check if address is validator
logger.debug('Check if address is a validator')
const isValidator = await validatorContract.methods.isValidator(address).call()
if (!isValidator) {
throw new InvalidValidatorError(`${address} is not a validator`)
}
throw new Error('Unknown error while processing message')
}
}
module.exports = estimateGas

@ -0,0 +1,110 @@
require('dotenv').config()
const { HttpListProviderError } = require('http-list-provider')
const promiseLimit = require('promise-limit')
const rootLogger = require('../../services/logger')
const { web3Home } = require('../../services/web3')
const bridgeValidatorsABI = require('../../../abis/BridgeValidators.abi')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const estimateGas = require('./estimateGas')
const { addTxHashToData, parseAMBMessage } = require('../../utils/message')
const { generateGasPriceOptions } = require('../../utils/utils')
const {
AlreadyProcessedError,
AlreadySignedError,
InvalidValidatorError
} = require('../../utils/errors')
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
let validatorContract = null
function processAffirmationRequestsBuilder(config) {
const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress)
return async function processAffirmationRequests(affirmationRequests) {
const txToSend = []
if (validatorContract === null) {
rootLogger.debug('Getting validator contract address')
const validatorContractAddress = await homeBridge.methods.validatorContract().call()
rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained')
validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress)
}
rootLogger.debug(`Processing ${affirmationRequests.length} AffirmationRequest events`)
const callbacks = affirmationRequests.map(affirmationRequest =>
limit(async () => {
const { encodedData } = affirmationRequest.returnValues
const logger = rootLogger.child({
eventTransactionHash: affirmationRequest.transactionHash
})
const message = addTxHashToData({
encodedData,
transactionHash: affirmationRequest.transactionHash
})
const { sender, executor, dataType, gasPrice, gasPriceSpeed } = parseAMBMessage(message)
logger.info(
{ sender, executor },
`Processing affirmationRequest ${affirmationRequest.transactionHash}`
)
let gasEstimate
try {
logger.debug('Estimate gas')
gasEstimate = await estimateGas({
web3: web3Home,
homeBridge,
validatorContract,
message,
address: config.validatorAddress
})
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error(
'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.'
)
} else if (e instanceof InvalidValidatorError) {
logger.fatal({ address: config.validatorAddress }, 'Invalid validator')
process.exit(EXIT_CODES.INCOMPATIBILITY)
} else if (e instanceof AlreadySignedError) {
logger.info(`Already signed affirmationRequest ${affirmationRequest.transactionHash}`)
return
} else if (e instanceof AlreadyProcessedError) {
logger.info(
`affirmationRequest ${
affirmationRequest.transactionHash
} was already processed by other validators`
)
return
} else {
logger.error(e, 'Unknown error while processing transaction')
throw e
}
}
const data = await homeBridge.methods.executeAffirmation(message).encodeABI()
const gasPriceOptions = generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed })
txToSend.push({
data,
gasEstimate,
transactionReference: affirmationRequest.transactionHash,
to: config.homeBridgeAddress,
gasPriceOptions
})
})
)
await Promise.all(callbacks)
return txToSend
}
}
module.exports = processAffirmationRequestsBuilder

@ -0,0 +1,67 @@
const Web3 = require('web3')
const { HttpListProviderError } = require('http-list-provider')
const {
AlreadyProcessedError,
IncompatibleContractError,
InvalidValidatorError
} = require('../../utils/errors')
const logger = require('../../services/logger').child({
module: 'processCollectedSignatures:estimateGas'
})
const web3 = new Web3()
const { toBN } = Web3.utils
async function estimateGas({
foreignBridge,
validatorContract,
message,
numberOfCollectedSignatures,
v,
r,
s,
txHash,
address
}) {
try {
const gasEstimate = await foreignBridge.methods
.executeSignatures(message, v, r, s)
.estimateGas({
from: address
})
return gasEstimate
} catch (e) {
if (e instanceof HttpListProviderError) {
throw e
}
// check if the message was already processed
logger.debug('Check if the message was already processed')
const alreadyProcessed = await foreignBridge.methods.relayedMessages(txHash).call()
if (alreadyProcessed) {
throw new AlreadyProcessedError()
}
// check if the number of signatures is enough
logger.debug('Check if number of signatures is enough')
const requiredSignatures = await validatorContract.methods.requiredSignatures().call()
if (toBN(requiredSignatures).gt(toBN(numberOfCollectedSignatures))) {
throw new IncompatibleContractError('The number of collected signatures does not match')
}
// check if all the signatures were made by validators
for (let i = 0; i < v.length; i++) {
const address = web3.eth.accounts.recover(message, web3.utils.toHex(v[i]), r[i], s[i])
logger.debug({ address }, 'Check that signature is from a validator')
const isValidator = await validatorContract.methods.isValidator(address).call()
if (!isValidator) {
throw new InvalidValidatorError(`Message signed by ${address} that is not a validator`)
}
}
throw new Error('Unknown error while processing message')
}
}
module.exports = estimateGas

@ -0,0 +1,140 @@
require('dotenv').config()
const promiseLimit = require('promise-limit')
const { HttpListProviderError } = require('http-list-provider')
const bridgeValidatorsABI = require('../../../abis/BridgeValidators.abi')
const rootLogger = require('../../services/logger')
const { web3Home, web3Foreign } = require('../../services/web3')
const { signatureToVRS, parseAMBMessage } = require('../../utils/message')
const { generateGasPriceOptions } = require('../../utils/utils')
const estimateGas = require('./estimateGas')
const {
AlreadyProcessedError,
IncompatibleContractError,
InvalidValidatorError
} = require('../../utils/errors')
const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
let validatorContract = null
function processCollectedSignaturesBuilder(config) {
const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress)
const foreignBridge = new web3Foreign.eth.Contract(
config.foreignBridgeAbi,
config.foreignBridgeAddress
)
return async function processCollectedSignatures(signatures) {
const txToSend = []
if (validatorContract === null) {
rootLogger.debug('Getting validator contract address')
const validatorContractAddress = await foreignBridge.methods.validatorContract().call()
rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained')
validatorContract = new web3Foreign.eth.Contract(
bridgeValidatorsABI,
validatorContractAddress
)
}
rootLogger.debug(`Processing ${signatures.length} CollectedSignatures events`)
const callbacks = signatures.map(colSignature =>
limit(async () => {
const {
authorityResponsibleForRelay,
messageHash,
NumberOfCollectedSignatures
} = colSignature.returnValues
const logger = rootLogger.child({
eventTransactionHash: colSignature.transactionHash
})
if (
authorityResponsibleForRelay === web3Home.utils.toChecksumAddress(config.validatorAddress)
) {
logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`)
const message = await homeBridge.methods.message(messageHash).call()
const requiredSignatures = []
requiredSignatures.length = NumberOfCollectedSignatures
requiredSignatures.fill(0)
const [v, r, s] = [[], [], []]
logger.debug('Getting message signatures')
const signaturePromises = requiredSignatures.map(async (el, index) => {
logger.debug({ index }, 'Getting message signature')
const signature = await homeBridge.methods.signature(messageHash, index).call()
const recover = signatureToVRS(signature)
v.push(recover.v)
r.push(recover.r)
s.push(recover.s)
})
await Promise.all(signaturePromises)
const { dataType, gasPrice, gasPriceSpeed, txHash } = parseAMBMessage(message)
let gasEstimate
try {
logger.debug('Estimate gas')
gasEstimate = await estimateGas({
foreignBridge,
validatorContract,
v,
r,
s,
message,
numberOfCollectedSignatures: NumberOfCollectedSignatures,
txHash,
address: config.validatorAddress
})
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error(
'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.'
)
} else if (e instanceof AlreadyProcessedError) {
logger.info(`Already processed CollectedSignatures ${colSignature.transactionHash}`)
return
} else if (
e instanceof IncompatibleContractError ||
e instanceof InvalidValidatorError
) {
logger.error(`The message couldn't be processed; skipping: ${e.message}`)
return
} else {
logger.error(e, 'Unknown error while processing transaction')
throw e
}
}
const data = await foreignBridge.methods.executeSignatures(message, v, r, s).encodeABI()
const gasPriceOptions = generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed })
txToSend.push({
data,
gasEstimate,
transactionReference: colSignature.transactionHash,
to: config.foreignBridgeAddress,
gasPriceOptions
})
} else {
logger.info(
`Validator not responsible for relaying CollectedSignatures ${
colSignature.transactionHash
}`
)
}
})
)
await Promise.all(callbacks)
return txToSend
}
}
module.exports = processCollectedSignaturesBuilder

@ -0,0 +1,112 @@
require('dotenv').config()
const promiseLimit = require('promise-limit')
const { HttpListProviderError } = require('http-list-provider')
const bridgeValidatorsABI = require('../../../abis/BridgeValidators.abi')
const rootLogger = require('../../services/logger')
const { web3Home } = require('../../services/web3')
const { addTxHashToData, parseAMBMessage } = require('../../utils/message')
const estimateGas = require('../processSignatureRequests/estimateGas')
const {
AlreadyProcessedError,
AlreadySignedError,
InvalidValidatorError
} = require('../../utils/errors')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const { VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
let validatorContract = null
function processSignatureRequestsBuilder(config) {
const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress)
return async function processSignatureRequests(signatureRequests) {
const txToSend = []
if (validatorContract === null) {
rootLogger.debug('Getting validator contract address')
const validatorContractAddress = await homeBridge.methods.validatorContract().call()
rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained')
validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress)
}
rootLogger.debug(`Processing ${signatureRequests.length} SignatureRequest events`)
const callbacks = signatureRequests.map(signatureRequest =>
limit(async () => {
const { encodedData } = signatureRequest.returnValues
const logger = rootLogger.child({
eventTransactionHash: signatureRequest.transactionHash
})
const message = addTxHashToData({
encodedData,
transactionHash: signatureRequest.transactionHash
})
const { sender, executor } = parseAMBMessage(message)
logger.info(
{ sender, executor },
`Processing signatureRequest ${signatureRequest.transactionHash}`
)
const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`)
let gasEstimate
try {
logger.debug('Estimate gas')
gasEstimate = await estimateGas({
web3: web3Home,
homeBridge,
validatorContract,
signature: signature.signature,
message,
address: config.validatorAddress
})
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error(
'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.'
)
} else if (e instanceof InvalidValidatorError) {
logger.fatal({ address: config.validatorAddress }, 'Invalid validator')
process.exit(EXIT_CODES.INCOMPATIBILITY)
} else if (e instanceof AlreadySignedError) {
logger.info(`Already signed signatureRequest ${signatureRequest.transactionHash}`)
return
} else if (e instanceof AlreadyProcessedError) {
logger.info(
`signatureRequest ${
signatureRequest.transactionHash
} was already processed by other validators`
)
return
} else {
logger.error(e, 'Unknown error while processing transaction')
throw e
}
}
const data = await homeBridge.methods
.submitSignature(signature.signature, message)
.encodeABI({ from: config.validatorAddress })
txToSend.push({
data,
gasEstimate,
transactionReference: signatureRequest.transactionHash,
to: config.homeBridgeAddress
})
})
)
await Promise.all(callbacks)
return txToSend
}
}
module.exports = processSignatureRequestsBuilder

@ -99,7 +99,6 @@ async function main({ msg, ackMsg, nackMsg, sendToQueue, channel }) {
const txArray = JSON.parse(msg.content) const txArray = JSON.parse(msg.content)
logger.info(`Msg received with ${txArray.length} Tx to send`) logger.info(`Msg received with ${txArray.length} Tx to send`)
const gasPrice = GasPrice.getPrice()
const ttl = REDIS_LOCK_TTL * txArray.length const ttl = REDIS_LOCK_TTL * txArray.length
@ -114,6 +113,7 @@ async function main({ msg, ackMsg, nackMsg, sendToQueue, channel }) {
logger.debug(`Sending ${txArray.length} transactions`) logger.debug(`Sending ${txArray.length} transactions`)
await syncForEach(txArray, async job => { await syncForEach(txArray, async job => {
const gasLimit = addExtraGas(job.gasEstimate, EXTRA_GAS_PERCENTAGE) const gasLimit = addExtraGas(job.gasEstimate, EXTRA_GAS_PERCENTAGE)
const gasPrice = GasPrice.getPrice(job.gasPriceOptions)
try { try {
logger.info(`Sending transaction with nonce ${nonce}`) logger.info(`Sending transaction with nonce ${nonce}`)
@ -121,7 +121,7 @@ async function main({ msg, ackMsg, nackMsg, sendToQueue, channel }) {
chain: config.id, chain: config.id,
data: job.data, data: job.data,
nonce, nonce,
gasPrice: gasPrice.toString(10), gasPrice,
amount: '0', amount: '0',
gasLimit, gasLimit,
privateKey: VALIDATOR_ADDRESS_PRIVATE_KEY, privateKey: VALIDATOR_ADDRESS_PRIVATE_KEY,

@ -7,7 +7,11 @@ const logger = require('../services/logger').child({
module: 'gasPrice' module: 'gasPrice'
}) })
const { setIntervalAndRun } = require('../utils/utils') const { setIntervalAndRun } = require('../utils/utils')
const { DEFAULT_UPDATE_INTERVAL, GAS_PRICE_BOUNDARIES } = require('../utils/constants') const {
DEFAULT_UPDATE_INTERVAL,
GAS_PRICE_BOUNDARIES,
GAS_PRICE_OPTIONS
} = require('../utils/constants')
const HomeABI = bridgeConfig.homeBridgeAbi const HomeABI = bridgeConfig.homeBridgeAbi
const ForeignABI = bridgeConfig.foreignBridgeAbi const ForeignABI = bridgeConfig.foreignBridgeAbi
@ -30,6 +34,7 @@ const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS)
const foreignBridge = new web3Foreign.eth.Contract(ForeignABI, FOREIGN_BRIDGE_ADDRESS) const foreignBridge = new web3Foreign.eth.Contract(ForeignABI, FOREIGN_BRIDGE_ADDRESS)
let cachedGasPrice = null let cachedGasPrice = null
let cachedGasPriceOracleSpeeds = null
function gasPriceWithinLimits(gasPrice) { function gasPriceWithinLimits(gasPrice) {
if (gasPrice < GAS_PRICE_BOUNDARIES.MIN) { if (gasPrice < GAS_PRICE_BOUNDARIES.MIN) {
@ -49,13 +54,19 @@ async function fetchGasPriceFromOracle(oracleUrl, speedType) {
throw new Error(`Response from Oracle didn't include gas price for ${speedType} type.`) throw new Error(`Response from Oracle didn't include gas price for ${speedType} type.`)
} }
const gasPrice = gasPriceWithinLimits(oracleGasPrice) const gasPrice = gasPriceWithinLimits(oracleGasPrice)
return Web3Utils.toWei(gasPrice.toString(), 'gwei') return {
oracleGasPrice: Web3Utils.toWei(gasPrice.toString(), 'gwei'),
oracleResponse: json
}
} }
async function fetchGasPrice({ bridgeContract, oracleFn }) { async function fetchGasPrice({ bridgeContract, oracleFn }) {
let gasPrice = null let gasPrice = null
let oracleGasPriceSpeeds = null
try { try {
gasPrice = await oracleFn() const { oracleGasPrice, oracleResponse } = await oracleFn()
gasPrice = oracleGasPrice
oracleGasPriceSpeeds = oracleResponse
logger.debug({ gasPrice }, 'Gas price updated using the oracle') logger.debug({ gasPrice }, 'Gas price updated using the oracle')
} catch (e) { } catch (e) {
logger.error(`Gas Price API is not available. ${e.message}`) logger.error(`Gas Price API is not available. ${e.message}`)
@ -67,7 +78,10 @@ async function fetchGasPrice({ bridgeContract, oracleFn }) {
logger.error(`There was a problem getting the gas price from the contract. ${e.message}`) logger.error(`There was a problem getting the gas price from the contract. ${e.message}`)
} }
} }
return gasPrice return {
gasPrice,
oracleGasPriceSpeeds
}
} }
let fetchGasPriceInterval = null let fetchGasPriceInterval = null
@ -98,21 +112,36 @@ async function start(chainId) {
} }
fetchGasPriceInterval = setIntervalAndRun(async () => { fetchGasPriceInterval = setIntervalAndRun(async () => {
const gasPrice = await fetchGasPrice({ const { gasPrice, oracleGasPriceSpeeds } = await fetchGasPrice({
bridgeContract, bridgeContract,
oracleFn: () => fetchGasPriceFromOracle(oracleUrl, speedType) oracleFn: () => fetchGasPriceFromOracle(oracleUrl, speedType)
}) })
cachedGasPrice = gasPrice || cachedGasPrice cachedGasPrice = gasPrice || cachedGasPrice
cachedGasPriceOracleSpeeds = oracleGasPriceSpeeds || cachedGasPriceOracleSpeeds
}, updateInterval) }, updateInterval)
} }
function getPrice() { function getPrice(options) {
return cachedGasPrice return processGasPriceOptions({ options, cachedGasPrice, cachedGasPriceOracleSpeeds })
}
function processGasPriceOptions({ options, cachedGasPrice, cachedGasPriceOracleSpeeds }) {
let gasPrice = cachedGasPrice
if (options && options.type && options.value) {
if (options.type === GAS_PRICE_OPTIONS.GAS_PRICE) {
return options.value
} else if (options.type === GAS_PRICE_OPTIONS.SPEED) {
const speedOption = cachedGasPriceOracleSpeeds[options.value]
gasPrice = speedOption ? Web3Utils.toWei(speedOption.toString(), 'gwei') : cachedGasPrice
}
}
return gasPrice
} }
module.exports = { module.exports = {
start, start,
fetchGasPrice, fetchGasPrice,
getPrice, getPrice,
processGasPriceOptions,
gasPriceWithinLimits gasPriceWithinLimits
} }

@ -20,5 +20,16 @@ module.exports = {
ERC_TYPES: { ERC_TYPES: {
ERC20: 'ERC20', ERC20: 'ERC20',
ERC677: 'ERC677' ERC677: 'ERC677'
},
GAS_PRICE_OPTIONS: {
UNDEFINED: '00',
GAS_PRICE: '01',
SPEED: '02'
},
ORACLE_GAS_PRICE_SPEEDS: {
SLOW: 'slow',
STANDARD: 'standard',
FAST: 'fast',
INSTANT: 'instant'
} }
} }

@ -1,6 +1,13 @@
const assert = require('assert') const assert = require('assert')
const Web3Utils = require('web3-utils') const Web3Utils = require('web3-utils')
const { ORACLE_GAS_PRICE_SPEEDS, GAS_PRICE_OPTIONS } = require('./constants')
const gasPriceSpeedMapper = {
'01': ORACLE_GAS_PRICE_SPEEDS.INSTANT,
'02': ORACLE_GAS_PRICE_SPEEDS.FAST,
'03': ORACLE_GAS_PRICE_SPEEDS.STANDARD,
'04': ORACLE_GAS_PRICE_SPEEDS.SLOW
}
// strips leading "0x" if present // strips leading "0x" if present
function strip0x(input) { function strip0x(input) {
return input.replace(/^0x/, '') return input.replace(/^0x/, '')
@ -72,8 +79,55 @@ function signatureToVRS(signature) {
return { v, r, s } return { v, r, s }
} }
function addTxHashToData({ encodedData, transactionHash }) {
return encodedData.slice(0, 82) + strip0x(transactionHash) + encodedData.slice(82)
}
function parseAMBMessage(message) {
message = strip0x(message)
const sender = `0x${message.slice(0, 40)}`
const executor = `0x${message.slice(40, 80)}`
const txHash = `0x${message.slice(80, 144)}`
const gasLimit = Web3Utils.toBN(message.slice(144, 208))
const dataType = message.slice(208, 210)
let gasPrice = null
let gasPriceSpeed = null
let dataStart = 210
switch (dataType) {
case GAS_PRICE_OPTIONS.GAS_PRICE:
gasPrice = Web3Utils.toBN(message.slice(210, 274))
dataStart += 64
break
case GAS_PRICE_OPTIONS.SPEED:
gasPriceSpeed = gasPriceSpeedMapper[message.slice(210, 212)]
dataStart += 2
break
case GAS_PRICE_OPTIONS.UNDEFINED:
default:
break
}
const data = `0x${message.slice(dataStart, message.length)}`
return {
sender,
executor,
txHash,
gasLimit,
dataType,
gasPrice,
gasPriceSpeed,
data
}
}
module.exports = { module.exports = {
createMessage, createMessage,
parseMessage, parseMessage,
signatureToVRS signatureToVRS,
addTxHashToData,
parseAMBMessage,
strip0x
} }

@ -1,6 +1,7 @@
const BigNumber = require('bignumber.js') const BigNumber = require('bignumber.js')
const promiseRetry = require('promise-retry') const promiseRetry = require('promise-retry')
const Web3 = require('web3') const Web3 = require('web3')
const { GAS_PRICE_OPTIONS } = require('./constants')
async function syncForEach(array, callback) { async function syncForEach(array, callback) {
for (let index = 0; index < array.length; index++) { for (let index = 0; index < array.length; index++) {
@ -96,6 +97,22 @@ function privateKeyToAddress(privateKey) {
: null : null
} }
function generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed }) {
let gasPriceOptions = null
if (dataType === GAS_PRICE_OPTIONS.GAS_PRICE) {
gasPriceOptions = {
type: dataType,
value: gasPrice
}
} else if (dataType === GAS_PRICE_OPTIONS.SPEED) {
gasPriceOptions = {
type: dataType,
value: gasPriceSpeed
}
}
return gasPriceOptions
}
module.exports = { module.exports = {
syncForEach, syncForEach,
checkHTTPS, checkHTTPS,
@ -103,5 +120,6 @@ module.exports = {
addExtraGas, addExtraGas,
setIntervalAndRun, setIntervalAndRun,
watchdog, watchdog,
privateKeyToAddress privateKeyToAddress,
generateGasPriceOptions
} }

@ -21,6 +21,9 @@ const processSignatureRequests = require('./events/processSignatureRequests')(co
const processCollectedSignatures = require('./events/processCollectedSignatures')(config) const processCollectedSignatures = require('./events/processCollectedSignatures')(config)
const processAffirmationRequests = require('./events/processAffirmationRequests')(config) const processAffirmationRequests = require('./events/processAffirmationRequests')(config)
const processTransfers = require('./events/processTransfers')(config) const processTransfers = require('./events/processTransfers')(config)
const processAMBSignatureRequests = require('./events/processAMBSignatureRequests')(config)
const processAMBCollectedSignatures = require('./events/processAMBCollectedSignatures')(config)
const processAMBAffirmationRequests = require('./events/processAMBAffirmationRequests')(config)
const ZERO = toBN(0) const ZERO = toBN(0)
const ONE = toBN(1) const ONE = toBN(1)
@ -100,6 +103,12 @@ function processEvents(events) {
case 'erc-erc-affirmation-request': case 'erc-erc-affirmation-request':
case 'erc-native-affirmation-request': case 'erc-native-affirmation-request':
return processTransfers(events) return processTransfers(events)
case 'amb-signature-request':
return processAMBSignatureRequests(events)
case 'amb-collected-signatures':
return processAMBCollectedSignatures(events)
case 'amb-affirmation-request':
return processAMBAffirmationRequests(events)
default: default:
return [] return []
} }

@ -1,11 +1,30 @@
const sinon = require('sinon') const sinon = require('sinon')
const { expect } = require('chai') const { expect } = require('chai')
const proxyquire = require('proxyquire').noPreserveCache() const proxyquire = require('proxyquire').noPreserveCache()
const { fetchGasPrice, gasPriceWithinLimits } = require('../src/services/gasPrice') const Web3Utils = require('web3-utils')
const { DEFAULT_UPDATE_INTERVAL, GAS_PRICE_BOUNDARIES } = require('../src/utils/constants') const {
fetchGasPrice,
processGasPriceOptions,
gasPriceWithinLimits
} = require('../src/services/gasPrice')
const {
DEFAULT_UPDATE_INTERVAL,
GAS_PRICE_OPTIONS,
ORACLE_GAS_PRICE_SPEEDS,
GAS_PRICE_BOUNDARIES
} = require('../src/utils/constants')
describe('gasPrice', () => { describe('gasPrice', () => {
describe('fetchGasPrice', () => { describe('fetchGasPrice', () => {
const oracleMockResponse = {
fast: 17.64,
block_time: 13.548,
health: true,
standard: 10.64,
block_number: 6704240,
instant: 51.9,
slow: 4.4
}
beforeEach(() => { beforeEach(() => {
sinon.stub(console, 'error') sinon.stub(console, 'error')
}) })
@ -15,7 +34,11 @@ describe('gasPrice', () => {
it('should fetch the gas price from the oracle by default', async () => { it('should fetch the gas price from the oracle by default', async () => {
// given // given
const oracleFnMock = () => Promise.resolve('1') const oracleFnMock = () =>
Promise.resolve({
oracleGasPrice: '1',
oracleResponse: oracleMockResponse
})
const bridgeContractMock = { const bridgeContractMock = {
methods: { methods: {
gasPrice: { gasPrice: {
@ -25,13 +48,14 @@ describe('gasPrice', () => {
} }
// when // when
const gasPrice = await fetchGasPrice({ const { gasPrice, oracleGasPriceSpeeds } = await fetchGasPrice({
bridgeContract: bridgeContractMock, bridgeContract: bridgeContractMock,
oracleFn: oracleFnMock oracleFn: oracleFnMock
}) })
// then // then
expect(gasPrice).to.equal('1') expect(gasPrice).to.equal('1')
expect(oracleGasPriceSpeeds).to.equal(oracleMockResponse)
}) })
it('should fetch the gas price from the contract if the oracle fails', async () => { it('should fetch the gas price from the contract if the oracle fails', async () => {
// given // given
@ -45,13 +69,14 @@ describe('gasPrice', () => {
} }
// when // when
const gasPrice = await fetchGasPrice({ const { gasPrice, oracleGasPriceSpeeds } = await fetchGasPrice({
bridgeContract: bridgeContractMock, bridgeContract: bridgeContractMock,
oracleFn: oracleFnMock oracleFn: oracleFnMock
}) })
// then // then
expect(gasPrice).to.equal('2') expect(gasPrice).to.equal('2')
expect(oracleGasPriceSpeeds).to.equal(null)
}) })
it('should return null if both the oracle and the contract fail', async () => { it('should return null if both the oracle and the contract fail', async () => {
// given // given
@ -65,13 +90,14 @@ describe('gasPrice', () => {
} }
// when // when
const gasPrice = await fetchGasPrice({ const { gasPrice, oracleGasPriceSpeeds } = await fetchGasPrice({
bridgeContract: bridgeContractMock, bridgeContract: bridgeContractMock,
oracleFn: oracleFnMock oracleFn: oracleFnMock
}) })
// then // then
expect(gasPrice).to.equal(null) expect(gasPrice).to.equal(null)
expect(oracleGasPriceSpeeds).to.equal(null)
}) })
}) })
describe('start', () => { describe('start', () => {
@ -176,4 +202,86 @@ describe('gasPrice', () => {
expect(gasPrice).to.equal(GAS_PRICE_BOUNDARIES.MAX) expect(gasPrice).to.equal(GAS_PRICE_BOUNDARIES.MAX)
}) })
}) })
describe('processGasPriceOptions', () => {
const oracleMockResponse = {
fast: 17.64,
block_time: 13.548,
health: true,
standard: 10.64,
block_number: 6704240,
instant: 51.9,
slow: 4.4
}
it('should return cached gas price if no options provided', async () => {
// given
const options = {}
const cachedGasPrice = '1000000000'
// when
const gasPrice = await processGasPriceOptions({
options,
cachedGasPrice,
cachedGasPriceOracleSpeeds: oracleMockResponse
})
// then
expect(gasPrice).to.equal(cachedGasPrice)
})
it('should return gas price provided by options', async () => {
// given
const options = {
type: GAS_PRICE_OPTIONS.GAS_PRICE,
value: '3000000000'
}
const cachedGasPrice = '1000000000'
// when
const gasPrice = await processGasPriceOptions({
options,
cachedGasPrice,
cachedGasPriceOracleSpeeds: oracleMockResponse
})
// then
expect(gasPrice).to.equal(options.value)
})
it('should return gas price provided by oracle speed option', async () => {
// given
const options = {
type: GAS_PRICE_OPTIONS.SPEED,
value: ORACLE_GAS_PRICE_SPEEDS.STANDARD
}
const cachedGasPrice = '1000000000'
const oracleGasPriceGwei = oracleMockResponse[ORACLE_GAS_PRICE_SPEEDS.STANDARD]
const oracleGasPrice = Web3Utils.toWei(oracleGasPriceGwei.toString(), 'gwei')
// when
const gasPrice = await processGasPriceOptions({
options,
cachedGasPrice,
cachedGasPriceOracleSpeeds: oracleMockResponse
})
// then
expect(gasPrice).to.equal(oracleGasPrice)
})
it('should return cached gas price if invalid speed option', async () => {
// given
const options = {
type: GAS_PRICE_OPTIONS.SPEED,
value: 'unknown'
}
const cachedGasPrice = '1000000000'
// when
const gasPrice = await processGasPriceOptions({
options,
cachedGasPrice,
cachedGasPriceOracleSpeeds: oracleMockResponse
})
// then
expect(gasPrice).to.equal(cachedGasPrice)
})
})
}) })

@ -1,6 +1,13 @@
const { BN, toBN } = require('web3').utils const { BN, toBN } = require('web3').utils
const { expect } = require('chai').use(require('bn-chai')(BN)) const { expect } = require('chai').use(require('bn-chai')(BN))
const { createMessage, parseMessage, signatureToVRS } = require('../src/utils/message') const {
createMessage,
parseMessage,
signatureToVRS,
parseAMBMessage,
strip0x
} = require('../src/utils/message')
const { ORACLE_GAS_PRICE_SPEEDS } = require('../src/utils/constants')
describe('message utils', () => { describe('message utils', () => {
const expectedMessageLength = 104 const expectedMessageLength = 104
@ -293,4 +300,107 @@ describe('message utils', () => {
expect(signatureThunk).to.throw() expect(signatureThunk).to.throw()
}) })
}) })
describe('parseAMBMessage', () => {
it('should parse data type 00', () => {
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
const msgTxHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
const msgDataType = '00'
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
const message = `0x${strip0x(msgSender)}${strip0x(msgExecutor)}${strip0x(
msgTxHash
)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
// when
const {
sender,
executor,
txHash,
gasLimit,
dataType,
gasPrice,
gasPriceSpeed,
data
} = parseAMBMessage(message)
// then
expect(sender).to.be.equal(msgSender)
expect(executor).to.be.equal(msgExecutor)
expect(txHash).to.be.equal(msgTxHash)
expect(gasLimit).to.eq.BN(toBN(msgGasLimit))
expect(dataType).to.be.equal(msgDataType)
expect(gasPrice).to.be.equal(null)
expect(gasPriceSpeed).to.be.equal(null)
expect(data).to.be.equal(msgData)
})
it('should parse data type 01', () => {
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
const msgTxHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
const msgDataType = '01'
const msgGasPrice = '0000000000000000000000000000000000000000000000000000000165a0bc00'
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
const message = `0x${strip0x(msgSender)}${strip0x(msgExecutor)}${strip0x(
msgTxHash
)}${msgGasLimit}${msgDataType}${msgGasPrice}${strip0x(msgData)}`
// when
const {
sender,
executor,
txHash,
gasLimit,
dataType,
gasPrice,
gasPriceSpeed,
data
} = parseAMBMessage(message)
// then
expect(sender).to.be.equal(msgSender)
expect(executor).to.be.equal(msgExecutor)
expect(txHash).to.be.equal(msgTxHash)
expect(gasLimit).to.eq.BN(toBN(msgGasLimit))
expect(dataType).to.be.equal(msgDataType)
expect(gasPrice).to.eq.BN(toBN(msgGasPrice))
expect(gasPriceSpeed).to.be.equal(null)
expect(data).to.be.equal(msgData)
})
it('should parse data type 02', () => {
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
const msgTxHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
const msgDataType = '02'
const msgGasPriceSpeed = '0x03'
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
const message = `0x${strip0x(msgSender)}${strip0x(msgExecutor)}${strip0x(
msgTxHash
)}${msgGasLimit}${msgDataType}${strip0x(msgGasPriceSpeed)}${strip0x(msgData)}`
// when
const {
sender,
executor,
txHash,
gasLimit,
dataType,
gasPrice,
gasPriceSpeed,
data
} = parseAMBMessage(message)
// then
expect(sender).to.be.equal(msgSender)
expect(executor).to.be.equal(msgExecutor)
expect(txHash).to.be.equal(msgTxHash)
expect(gasLimit).to.eq.BN(toBN(msgGasLimit))
expect(dataType).to.be.equal(msgDataType)
expect(gasPrice).to.be.equal(null)
expect(gasPriceSpeed).to.be.equal(ORACLE_GAS_PRICE_SPEEDS.STANDARD)
expect(data).to.be.equal(msgData)
})
})
}) })

@ -3,7 +3,8 @@ const chai = require('chai')
const chaiAsPromised = require('chai-as-promised') const chaiAsPromised = require('chai-as-promised')
const BigNumber = require('bignumber.js') const BigNumber = require('bignumber.js')
const proxyquire = require('proxyquire') const proxyquire = require('proxyquire')
const { addExtraGas, syncForEach } = require('../src/utils/utils') const { addExtraGas, syncForEach, generateGasPriceOptions } = require('../src/utils/utils')
const { GAS_PRICE_OPTIONS, ORACLE_GAS_PRICE_SPEEDS } = require('../src/utils/constants')
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const { expect } = chai const { expect } = chai
@ -134,4 +135,54 @@ describe('utils', () => {
}) })
}) })
}) })
describe('generateGasPriceOptions', () => {
it('should work for GAS_PRICE option', () => {
// given
const dataType = GAS_PRICE_OPTIONS.GAS_PRICE
const gasPrice = BigNumber('0000000000000000000000000000000000000000000000000000000165a0bc00')
const gasPriceSpeed = null
const expectedResult = {
type: dataType,
value: gasPrice
}
// when
const gasPriceOptions = generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed })
// then
expect(gasPriceOptions.type).to.be.equal(expectedResult.type)
expect(gasPriceOptions.value).to.be.equal(expectedResult.value)
})
it('should work for SPEED option', () => {
// given
const dataType = GAS_PRICE_OPTIONS.SPEED
const gasPrice = null
const gasPriceSpeed = ORACLE_GAS_PRICE_SPEEDS.STANDARD
const expectedResult = {
type: dataType,
value: gasPriceSpeed
}
// when
const gasPriceOptions = generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed })
// then
expect(gasPriceOptions.type).to.be.equal(expectedResult.type)
expect(gasPriceOptions.value).to.be.equal(expectedResult.value)
})
it('should return null option for undefined option', () => {
// given
const dataType = GAS_PRICE_OPTIONS.UNDEFINED
const gasPrice = null
const gasPriceSpeed = null
// when
const gasPriceOptions = generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed })
// then
expect(gasPriceOptions).to.be.equal(null)
})
})
}) })