diff --git a/contracts/gas-service/AxelarGasService.sol b/contracts/gas-service/AxelarGasService.sol index 5b43efe5..4c2d3abc 100644 --- a/contracts/gas-service/AxelarGasService.sol +++ b/contracts/gas-service/AxelarGasService.sol @@ -8,6 +8,18 @@ import '../util/Upgradable.sol'; // This should be owned by the microservice that is paying for gas. contract AxelarGasService is Upgradable, IAxelarGasService { + address public immutable gasCollector; + + constructor(address gasCollector_) { + gasCollector = gasCollector_; + } + + modifier onlyCollector() { + if (msg.sender != gasCollector) revert NotCollector(); + + _; + } + // This is called on the source chain before calling the gateway to execute a remote contract. function payGasForContractCall( address sender, @@ -117,18 +129,25 @@ contract AxelarGasService is Upgradable, IAxelarGasService { emit NativeGasAdded(txHash, logIndex, msg.value, refundAddress); } - function collectFees(address payable receiver, address[] calldata tokens) external onlyOwner { + function collectFees( + address payable receiver, + address[] calldata tokens, + uint256[] calldata amounts + ) external onlyCollector { if (receiver == address(0)) revert InvalidAddress(); - for (uint256 i; i < tokens.length; i++) { + uint256 tokensLength = tokens.length; + if (tokensLength != amounts.length) revert InvalidAmounts(); + + for (uint256 i; i < tokensLength; i++) { address token = tokens[i]; + uint256 amount = amounts[i]; + if (amount == 0) revert InvalidAmounts(); if (token == address(0)) { - uint256 amount = address(this).balance; - if (amount > 0) receiver.transfer(amount); + if (amount <= address(this).balance) receiver.transfer(amount); } else { - uint256 amount = IERC20(token).balanceOf(address(this)); - if (amount > 0) _safeTransfer(token, receiver, amount); + if (amount <= IERC20(token).balanceOf(address(this))) _safeTransfer(token, receiver, amount); } } } @@ -137,7 +156,7 @@ contract AxelarGasService is Upgradable, IAxelarGasService { address payable receiver, address token, uint256 amount - ) external onlyOwner { + ) external onlyCollector { if (receiver == address(0)) revert InvalidAddress(); if (token == address(0)) { diff --git a/contracts/interfaces/IAxelarGasService.sol b/contracts/interfaces/IAxelarGasService.sol index d8d4b00c..33bd4646 100644 --- a/contracts/interfaces/IAxelarGasService.sol +++ b/contracts/interfaces/IAxelarGasService.sol @@ -9,6 +9,8 @@ interface IAxelarGasService is IUpgradable { error NothingReceived(); error TransferFailed(); error InvalidAddress(); + error NotCollector(); + error InvalidAmounts(); event GasPaidForContractCall( address indexed sourceAddress, @@ -114,11 +116,17 @@ interface IAxelarGasService is IUpgradable { address refundAddress ) external payable; - function collectFees(address payable receiver, address[] calldata tokens) external; + function collectFees( + address payable receiver, + address[] calldata tokens, + uint256[] calldata amounts + ) external; function refund( address payable receiver, address token, uint256 amount ) external; + + function gasCollector() external returns (address); } diff --git a/info/mainnet.json b/info/mainnet.json index 8005a9e3..3384676c 100644 --- a/info/mainnet.json +++ b/info/mainnet.json @@ -4,6 +4,7 @@ "chainId": 1, "rpc": "https://mainnet.infura.io/v3/a4812158fbab4a2aaa849e6f4a6dc605", "gateway": "0x4F4495243837681061C4743b74B3eEdf548D56A5", + "gasCollector": "0xa57adce1d2fe72949e4308867d894cd7e7de0ef2", "gasReceiver": "0x4154CF6eea0633DD9c4933E76a077fD7E9260738", "constAddressDeployer": "0x617179a15fEAa53Fa82ae80b0fc3E85b7359a748", "tokenName": "Ether", @@ -15,6 +16,7 @@ "chainId": 43114, "rpc": "https://api.avax.network/ext/bc/C/rpc", "gateway": "0x5029C0EFf6C34351a0CEc334542cDb22c7928f78", + "gasCollector": "0xa57adce1d2fe72949e4308867d894cd7e7de0ef2", "gasReceiver": "0xB53C693544363912D2A034f70D9d98808D5E192a", "constAddressDeployer": "0x617179a15fEAa53Fa82ae80b0fc3E85b7359a748", "tokenName": "Avax", @@ -26,6 +28,7 @@ "chainId": 250, "gateway": "0x304acf330bbE08d1e512eefaa92F6a57871fD895", "rpc": "https://rpc.ftm.tools/", + "gasCollector": "0xa57adce1d2fe72949e4308867d894cd7e7de0ef2", "gasReceiver": "0x2879da536D9d107D6b92D95D7c4CFaA5De7088f4", "constAddressDeployer": "0x617179a15fEAa53Fa82ae80b0fc3E85b7359a748", "tokenName": "Fantom", @@ -37,6 +40,7 @@ "chainId": 137, "gateway": "0x6f015F16De9fC8791b234eF68D486d2bF203FBA8", "rpc": "https://polygon-rpc.com/", + "gasCollector": "0xa57adce1d2fe72949e4308867d894cd7e7de0ef2", "gasReceiver": "0xc8E0b617c388c7E800a7643adDD01218E14a727a", "constAddressDeployer": "0x617179a15fEAa53Fa82ae80b0fc3E85b7359a748", "tokenName": "Matic", @@ -48,6 +52,7 @@ "chainId": 1284, "gateway": "0x4F4495243837681061C4743b74B3eEdf548D56A5", "rpc": "https://rpc.api.moonbeam.network", + "gasCollector": "0xa57adce1d2fe72949e4308867d894cd7e7de0ef2", "gasReceiver": "0x27927CD55db998b720214205e598aA9AD614AEE3", "constAddressDeployer": "0x617179a15fEAa53Fa82ae80b0fc3E85b7359a748", "tokenName": "Glimmer", diff --git a/info/testnet.json b/info/testnet.json index b46539cc..ef27a360 100644 --- a/info/testnet.json +++ b/info/testnet.json @@ -4,6 +4,7 @@ "chainId": 3, "rpc": "https://ropsten.infura.io/v3/a4812158fbab4a2aaa849e6f4a6dc605", "gateway": "0xBC6fcce7c5487d43830a219CA6E7B83238B41e71", + "gasCollector": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", "constAddressDeployer": "0x617179a15fEAa53Fa82ae80b0fc3E85b7359a748", "tokenName": "Ether", @@ -16,6 +17,7 @@ "chainId": 43113, "rpc": "https://api.avax-test.network/ext/bc/C/rpc", "gateway": "0xC249632c2D40b9001FE907806902f63038B737Ab", + "gasCollector": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", "constAddressDeployer": "0x617179a15fEAa53Fa82ae80b0fc3E85b7359a748", "tokenName": "Avax", @@ -28,6 +30,7 @@ "chainId": 4002, "gateway": "0x97837985Ec0494E7b9C71f5D3f9250188477ae14", "rpc": "https://rpc.testnet.fantom.network", + "gasCollector": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", "constAddressDeployer": "0x617179a15fEAa53Fa82ae80b0fc3E85b7359a748", "tokenName": "Fantom", @@ -40,6 +43,7 @@ "chainId": 80001, "gateway": "0xBF62ef1486468a6bd26Dd669C06db43dEd5B849B", "rpc": "https://polygon-mumbai.g.alchemy.com/v2/Ksd4J1QVWaOJAJJNbr_nzTcJBJU-6uP3", + "gasCollector": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", "constAddressDeployer": "0x617179a15fEAa53Fa82ae80b0fc3E85b7359a748", "tokenName": "Matic", @@ -52,6 +56,7 @@ "chainId": 1287, "gateway": "0x5769D84DD62a6fD969856c75c7D321b84d455929", "rpc": "https://moonbeam-alpha.api.onfinality.io/public", + "gasCollector": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", "constAddressDeployer": "0x617179a15fEAa53Fa82ae80b0fc3E85b7359a748", "tokenName": "DEV", @@ -64,6 +69,7 @@ "chainId": 97, "gateway": "0x4D147dCb984e6affEEC47e44293DA442580A3Ec0", "rpc": "https://data-seed-prebsc-1-s1.binance.org:8545/", + "gasCollector": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", "constAddressDeployer": "0x617179a15fEAa53Fa82ae80b0fc3E85b7359a748", "tokenName": "Binance Coin", diff --git a/scripts/deploy-upgradable.js b/scripts/deploy-upgradable.js index 98e738c0..5b0b27b0 100644 --- a/scripts/deploy-upgradable.js +++ b/scripts/deploy-upgradable.js @@ -6,14 +6,21 @@ const readlineSync = require('readline-sync'); const { outputJsonSync } = require('fs-extra'); const { defaultAbiCoder } = require('ethers/lib/utils'); +function getImplementationArgs(contractName, chain) { + if (contractName === 'AxelarGasService') return [chain.gasCollector]; + if (contractName === 'AxelarDepositService') return []; + throw new Error(`${contractName} is not supported.`); +} + function getInitArgs(contractName, chain) { - if (contractName == 'AxelarGasService') return '0x'; - if (contractName == 'AxelarDepositService') return defaultAbiCoder.encode(['address', 'string'], [chain.gateway, chain.wrappedSymbol]); + if (contractName === 'AxelarGasService') return '0x'; + if (contractName === 'AxelarDepositService') return defaultAbiCoder.encode(['address', 'string'], [chain.gateway, chain.wrappedSymbol]); throw new Error(`${contractName} is not supported.`); } + function getUpgradeArgs(contractName, chain) { - if (contractName == 'AxelarGasService') return '0x'; - if (contractName == 'AxelarDepositService') return '0x'; + if (contractName === 'AxelarGasService') return '0x'; + if (contractName === 'AxelarDepositService') return '0x'; throw new Error(`${contractName} is not supported.`); } @@ -30,19 +37,25 @@ async function deploy(env, chains, wallet, artifactPath, contractName, deployTo) const implementationJson = require(implementationPath); const proxyJson = require(proxyPath); for (const chain of chains) { - if (deployTo.length > 0 && deployTo.find((name) => chain.name == name) == null) continue; + if (deployTo.length > 0 && deployTo.find((name) => chain.name === name) === null) continue; const rpc = chain.rpc; const provider = getDefaultProvider(rpc); console.log(`Deployer has ${(await provider.getBalance(wallet.address)) / 1e18} ${chain.tokenSymbol} on ${chain.name}.`); } const anwser = readlineSync.question('Proceed with deployment? (y/n). '); - if (anwser != 'y') return; + if (anwser !== 'y') return; for (const chain of chains) { - if (deployTo.length > 0 && deployTo.find((name) => chain.name == name) == null) continue; + if (deployTo.length > 0 && deployTo.find((name) => chain.name === name) === null) continue; const rpc = chain.rpc; const provider = getDefaultProvider(rpc); if (chain[contractName]) { - await upgradeUpgradable(chain[contractName], implementationJson, getUpgradeArgs(contractName, chain), wallet.connect(provider)); + await upgradeUpgradable( + wallet.connect(provider), + chain[contractName], + implementationJson, + getImplementationArgs(contractName, chain), + getUpgradeArgs(contractName, chain), + ); console.log(`${chain.name} | Upgraded.`); } else { const key = contractName; @@ -51,6 +64,7 @@ async function deploy(env, chains, wallet, artifactPath, contractName, deployTo) wallet.connect(provider), implementationJson, proxyJson, + getImplementationArgs(contractName, chain), getInitArgs(contractName, chain), key, ); @@ -63,7 +77,7 @@ async function deploy(env, chains, wallet, artifactPath, contractName, deployTo) if (require.main === module) { const env = process.argv[2]; - if (env == null || (env != 'testnet' && env != 'mainnet')) + if (env === null || (env !== 'testnet' && env !== 'mainnet')) throw new Error('Need to specify tesntet or local as an argument to this script.'); const chains = require(`../info/${env}.json`); diff --git a/scripts/upgradable.js b/scripts/upgradable.js index d35a6ba3..a897e99d 100644 --- a/scripts/upgradable.js +++ b/scripts/upgradable.js @@ -36,12 +36,12 @@ async function deployUpgradable( return new Contract(proxy.address, implementationJson.abi, wallet); } -async function upgradeUpgradable(proxyAddress, contractJson, setupParams, wallet) { +async function upgradeUpgradable(wallet, proxyAddress, contractJson, implementationParams = [], setupParams = '0x') { const proxy = new Contract(proxyAddress, IUpgradable.abi, wallet); const implementationFactory = new ContractFactory(contractJson.abi, contractJson.bytecode, wallet); - const implementation = await implementationFactory.deploy(); + const implementation = await implementationFactory.deploy(...implementationParams); await implementation.deployed(); const implementationCode = await wallet.provider.getCode(implementation.address); diff --git a/test/gmp/AxelarGasService.js b/test/gmp/AxelarGasService.js index 07e709c0..7b3128f0 100644 --- a/test/gmp/AxelarGasService.js +++ b/test/gmp/AxelarGasService.js @@ -26,7 +26,7 @@ describe('AxelarGasService', () => { beforeEach(async () => { const constAddressDeployer = await deployContract(ownerWallet, ConstAddressDeployer); - gasService = await deployUpgradable(constAddressDeployer.address, ownerWallet, GasService, GasServiceProxy); + gasService = await deployUpgradable(constAddressDeployer.address, ownerWallet, GasService, GasServiceProxy, [ownerWallet.address]); const name = 'testToken'; const symbol = 'testToken'; @@ -201,9 +201,17 @@ describe('AxelarGasService', () => { .and.to.emit(testToken, 'Transfer') .withArgs(gasService.address, userWallet.address, gasFeeAmount); - await expect(gasService.connect(userWallet).collectFees(ownerWallet.address, [ADDRESS_ZERO, testToken.address])).to.be.reverted; + await expect( + gasService + .connect(userWallet) + .collectFees(ownerWallet.address, [ADDRESS_ZERO, testToken.address], [nativeGasFeeAmount, gasFeeAmount]), + ).to.be.reverted; - await expect(await gasService.connect(ownerWallet).collectFees(ownerWallet.address, [ADDRESS_ZERO, testToken.address])) + await expect( + await gasService + .connect(ownerWallet) + .collectFees(ownerWallet.address, [ADDRESS_ZERO, testToken.address], [nativeGasFeeAmount, gasFeeAmount]), + ) .to.changeEtherBalance(ownerWallet, nativeGasFeeAmount) .and.to.emit(testToken, 'Transfer') .withArgs(gasService.address, ownerWallet.address, gasFeeAmount); @@ -211,7 +219,10 @@ describe('AxelarGasService', () => { it('should upgrade the gas receiver implementation', async () => { const prevImpl = await gasService.implementation(); - await expect(upgradeUpgradable(gasService.address, GasService, '0x', ownerWallet)).to.emit(gasService, 'Upgraded'); + await expect(upgradeUpgradable(ownerWallet, gasService.address, GasService, [ownerWallet.address])).to.emit( + gasService, + 'Upgraded', + ); const newImpl = await gasService.implementation(); expect(await gasService.owner()).to.be.equal(ownerWallet.address); diff --git a/test/gmp/GeneralMessagePassing.js b/test/gmp/GeneralMessagePassing.js index c1426095..d64ce48d 100644 --- a/test/gmp/GeneralMessagePassing.js +++ b/test/gmp/GeneralMessagePassing.js @@ -106,7 +106,9 @@ describe('GeneralMessagePassing', () => { destinationChainGateway = await deployGateway(); const constAddressDeployer = await deployContract(ownerWallet, ConstAddressDeployer); - sourceChainGasService = await deployUpgradable(constAddressDeployer.address, ownerWallet, GasService, GasServiceProxy); + sourceChainGasService = await deployUpgradable(constAddressDeployer.address, ownerWallet, GasService, GasServiceProxy, [ + ownerWallet.address, + ]); tokenA = await deployContract(ownerWallet, MintableCappedERC20, [nameA, symbolA, decimals, capacity]); tokenB = await deployContract(ownerWallet, MintableCappedERC20, [nameB, symbolB, decimals, capacity]);