Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(amplifier)!: amplifier gateway v2 #154

Merged
merged 46 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c004590
feat(auth)!: amplifier compatible auth contract
milapsheth Apr 5, 2024
5d04836
fix tests
milapsheth Apr 7, 2024
79f81dd
rename types file
milapsheth Apr 7, 2024
11e5c9b
rename storage function
milapsheth Apr 7, 2024
9144fba
rename hashMessage
milapsheth Apr 7, 2024
47d0327
switch to auth contract
milapsheth Apr 7, 2024
8b93d1b
cache variable
milapsheth Apr 7, 2024
b36cfff
optimize validation
milapsheth Apr 7, 2024
5fb14c9
add gas profiling test
milapsheth Apr 7, 2024
d4fbe65
refactor
milapsheth Apr 7, 2024
7e50528
fix interchain multisig tests
milapsheth Apr 7, 2024
f3bf7bb
fix amplifier gateway tests
milapsheth Apr 7, 2024
2e05dc3
owner test
milapsheth Apr 7, 2024
9204cac
lint
milapsheth Apr 7, 2024
eed3940
rename auth contract and refactor auth interface
milapsheth Apr 7, 2024
e4e6f42
fix tests
milapsheth Apr 7, 2024
c9b6fd4
rename multisig test
milapsheth Apr 7, 2024
2ba1e6c
add simplified multisig test
milapsheth Apr 7, 2024
0a37163
Merge branch 'main' into refactor/weighted-multisig
milapsheth Apr 7, 2024
ff51dec
codecov issue
milapsheth Apr 8, 2024
3b88fc3
revert
milapsheth Apr 8, 2024
5997b1f
interchain multisig coverage
milapsheth Apr 8, 2024
1d3f208
Merge branch 'main' into refactor/weighted-multisig
milapsheth Apr 8, 2024
082769c
feat(amplifier)!: amplifier gateway v2
milapsheth Apr 8, 2024
1b12848
comment out amplifier gateway test
milapsheth Apr 8, 2024
c29daa5
prettier
milapsheth Apr 8, 2024
a12e849
slither
milapsheth Apr 8, 2024
6b3717f
remove test imports
milapsheth Apr 8, 2024
4bfaec1
fix command id derivation
milapsheth Apr 8, 2024
009f7f3
fix tests
milapsheth Apr 9, 2024
e4f7888
more test improvements
milapsheth Apr 9, 2024
5a6be90
fix signer indexing and add more tests
milapsheth Apr 9, 2024
cc38f4a
Merge branch 'refactor/weighted-multisig' into feat/amplifier-gateway-v2
milapsheth Apr 9, 2024
c17bd6c
_validateProof now accepts Proof type
milapsheth Apr 9, 2024
9773366
pin slither version
milapsheth Apr 9, 2024
432097d
fix arg
milapsheth Apr 9, 2024
e8b5c0f
pin node version
milapsheth Apr 9, 2024
c9454d8
add randomized signer rotation test
milapsheth Apr 10, 2024
c97b542
prettier
milapsheth Apr 10, 2024
39fb161
remove outer named tuple
milapsheth Apr 10, 2024
e376d7a
Merge branch 'refactor/weighted-multisig' into feat/amplifier-gateway-v2
milapsheth Apr 10, 2024
21dd7c9
Merge branch 'main' into feat/amplifier-gateway-v2
milapsheth Apr 10, 2024
0e52b8b
check command execution
milapsheth Apr 10, 2024
c335dde
add remaining test coverage
milapsheth Apr 10, 2024
a006f21
reorder command execution for consistency
milapsheth Apr 10, 2024
8d5160a
more reordering
milapsheth Apr 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions contracts/gateway/AxelarAmplifierAuth.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IAxelarAmplifierAuth } from '../interfaces/IAxelarAmplifierAuth.sol';
// import { IAxelarAmplifierGatewayAuth } from '../interfaces/IAxelarAmplifierGatewayAuth.sol';

import { BaseWeightedMultisig } from '../governance/BaseWeightedMultisig.sol';
import { Ownable } from '../utils/Ownable.sol';
import { WeightedSigners } from '../types/WeightedMultisigTypes.sol';

/**
* @title AxelarAmplifierAuth Contract
* @dev This contract is used by the Axelar Gateway to verify signed commands. It inherits the BaseWeightedMultisig contract
* with added functionality to approve and execute multisig proposals.
*/
contract AxelarAmplifierAuth is Ownable, BaseWeightedMultisig, IAxelarAmplifierAuth {
/**
* @notice Initializes the contract.
* @dev Ownership of this contract should be transferred to the Gateway contract after deployment.
* @param owner_ The owner of the contract
* @param domainSeparator_ The domain separator for the signer proof
* @param initialSigners The initial weighted signers to be added to the auth contract
*/
constructor(
address owner_,
bytes32 domainSeparator_,
uint256 previousSignersRetention_,
bytes[] memory initialSigners
) Ownable(owner_) BaseWeightedMultisig(previousSignersRetention_, domainSeparator_) {
uint256 length = initialSigners.length;

for (uint256 i; i < length; ++i) {
WeightedSigners memory signers = abi.decode(initialSigners[i], (WeightedSigners));

_rotateSigners(signers);
}
}

/**
* @notice Rotate to a new set of weighted signers
* @param newSigners The ABI encoded WeightedSigners
*/
function rotateSigners(bytes calldata newSigners) external onlyOwner {
WeightedSigners memory signers = abi.decode(newSigners, (WeightedSigners));

_rotateSigners(signers);
}

/**
* @notice This function takes dataHash and proof data and reverts if proof is invalid
* @param dataHash The hash of the message that was signed
* @param proof The data containing signers with signatures
* @return isLatestSigners True if provided signers are the current ones
*/
function validateProof(bytes32 dataHash, bytes calldata proof) external view returns (bool isLatestSigners) {
return _validateProof(dataHash, proof);
}
}
229 changes: 140 additions & 89 deletions contracts/gateway/AxelarAmplifierGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
pragma solidity ^0.8.0;

import { IAxelarAmplifierGateway } from '../interfaces/IAxelarAmplifierGateway.sol';
import { IAxelarGatewayWeightedAuth } from '../interfaces/IAxelarGatewayWeightedAuth.sol';

import { ECDSA } from '../libs/ECDSA.sol';
import { IAxelarAmplifierGatewayAuth } from '../interfaces/IAxelarAmplifierGatewayAuth.sol';
import { CommandType, Message } from '../types/AmplifierGatewayTypes.sol';

contract AxelarAmplifierGateway is IAxelarAmplifierGateway {
/// @dev This slot contains all the storage for this contract in an upgrade-compatible manner
// keccak256('AxelarAmplifierGateway.Slot') - 1;
bytes32 internal constant AXELAR_AMPLIFIER_GATEWAY_SLOT =
0xca458dc12368669a3b8c292bc21c1b887ab1aa386fa3fcc1ed972afd74a330ca;
Expand All @@ -17,15 +17,12 @@ contract AxelarAmplifierGateway is IAxelarAmplifierGateway {
mapping(bytes32 => bool) approvals;
}

bytes32 internal constant SELECTOR_APPROVE_CONTRACT_CALL = keccak256('approveContractCall');
bytes32 internal constant SELECTOR_TRANSFER_OPERATORSHIP = keccak256('transferOperatorship');

IAxelarGatewayWeightedAuth public immutable authModule;
IAxelarAmplifierGatewayAuth public immutable authModule;

constructor(address authModule_) {
if (authModule_.code.length == 0) revert InvalidAuthModule();

authModule = IAxelarGatewayWeightedAuth(authModule_);
authModule = IAxelarAmplifierGatewayAuth(authModule_);
}

/******************\
Expand All @@ -47,14 +44,18 @@ contract AxelarAmplifierGateway is IAxelarAmplifierGateway {
address contractAddress,
bytes32 payloadHash
) external view override returns (bool) {
bytes32 key = _getIsContractCallApprovedKey(
commandId,
sourceChain,
sourceAddress,
contractAddress,
payloadHash
);
return _axelarAmplifierGatewayStorage().approvals[key];
return _isContractCallApproved(commandId, sourceChain, sourceAddress, contractAddress, payloadHash);
}

function isMessageApproved(
string calldata messageId,
string calldata sourceChain,
string calldata sourceAddress,
address contractAddress,
bytes32 payloadHash
) external view override returns (bool) {
bytes32 commandId = messageToCommandId(sourceChain, messageId);
return _isContractCallApproved(commandId, sourceChain, sourceAddress, contractAddress, payloadHash);
}

function validateContractCall(
Expand All @@ -63,119 +64,169 @@ contract AxelarAmplifierGateway is IAxelarAmplifierGateway {
string calldata sourceAddress,
bytes32 payloadHash
) external override returns (bool valid) {
bytes32 key = _getIsContractCallApprovedKey(commandId, sourceChain, sourceAddress, msg.sender, payloadHash);
valid = _axelarAmplifierGatewayStorage().approvals[key];

if (valid) {
delete _axelarAmplifierGatewayStorage().approvals[key];

emit ContractCallExecuted(commandId);
}
valid = _validateContractCall(commandId, sourceChain, sourceAddress, payloadHash);
}

/***********\
|* Getters *|
\***********/
function validateMessage(
string calldata messageId,
string calldata sourceChain,
string calldata sourceAddress,
bytes32 payloadHash
) external override returns (bool valid) {
bytes32 commandId = messageToCommandId(sourceChain, messageId);
valid = _validateContractCall(commandId, sourceChain, sourceAddress, payloadHash);
}

function isCommandExecuted(bytes32 commandId) public view override returns (bool) {
return _axelarAmplifierGatewayStorage().commands[commandId];
return _storage().commands[commandId];
}

/**
* @notice Compute the commandId for a `Message`.
* @param sourceChain The name of the source chain as registered on Axelar.
* @param messageId The unique message id for the message.
* @return The commandId for the message.
*/
function messageToCommandId(string calldata sourceChain, string calldata messageId) public pure returns (bytes32) {
// Axelar prevents `sourceChain` to contain '_',
// hence we can use it as a separator with abi.encodePacked to avoid ambiguous encodings
return keccak256(abi.encodePacked(CommandType.ApproveMessages, sourceChain, '_', messageId));
}

/**********************\
|* External Functions *|
\**********************/

function execute(bytes calldata batch) external {
(bytes memory data, bytes memory proof) = abi.decode(batch, (bytes, bytes));

bytes32 messageHash = ECDSA.toEthSignedMessageHash(keccak256(data));

// returns true for current operators
bool allowOperatorshipTransfer = authModule.validateProof(messageHash, proof);

uint256 chainId;
bytes32[] memory commandIds;
string[] memory commands;
bytes[] memory params;

(chainId, commandIds, commands, params) = abi.decode(data, (uint256, bytes32[], string[], bytes[]));

if (chainId != block.chainid) revert InvalidChainId();
/**
* @notice Approves an array of messages, signed by the Axelar signers.
* @param messages The array of messages to verify.
* @param proof The proof signed by the Axelar signers for this command.
*/
function approveMessages(Message[] calldata messages, bytes calldata proof) external {
bytes32 dataHash = _computeDataHash(CommandType.ApproveMessages, abi.encode(messages));

uint256 commandsLength = commandIds.length;
_verifyProof(dataHash, proof);

if (commandsLength != commands.length || commandsLength != params.length) revert InvalidCommands();
uint256 length = messages.length;
if (length == 0) revert InvalidMessages();

for (uint256 i; i < commandsLength; ++i) {
bytes32 commandId = commandIds[i];
for (uint256 i; i < length; ++i) {
Message calldata message = messages[i];
bytes32 commandId = messageToCommandId(message.sourceChain, message.messageId);

// Ignore if commandId is already executed
// Ignore if message has already been approved
if (isCommandExecuted(commandId)) {
continue;
}

bytes32 commandHash = keccak256(abi.encodePacked(commands[i]));
_approveMessage(commandId, message);

if (commandHash == SELECTOR_APPROVE_CONTRACT_CALL) {
_approveContractCall(params[i], commandId);
} else if (commandHash == SELECTOR_TRANSFER_OPERATORSHIP) {
if (!allowOperatorshipTransfer) {
continue;
}
_storage().commands[commandId] = true;

allowOperatorshipTransfer = false;
// slither-disable-next-line reentrancy-events
emit Executed(commandId);
}
}

_transferOperatorship(params[i]);
} else {
revert InvalidCommand(commandHash);
}
/**
* @notice Update the signer data for the auth module.
* @param newSignersData The data for the new signers.
* @param proof The proof signed by the Axelar verifiers for this command.
*/
function rotateSigners(bytes calldata newSignersData, bytes calldata proof) external {
bytes32 dataHash = _computeDataHash(CommandType.RotateSigners, newSignersData);
bytes32 commandId = dataHash;

_axelarAmplifierGatewayStorage().commands[commandId] = true;
if (isCommandExecuted(commandId)) {
revert CommandAlreadyExecuted(commandId);
}

// slither-disable-next-line reentrancy-events
emit Executed(commandId);
bool isLatestSigners = _verifyProof(dataHash, proof);
if (!isLatestSigners) {
revert NotLatestSigners();
}

authModule.rotateSigners(newSignersData);

// slither-disable-next-line reentrancy-events
emit SignersRotated(newSignersData);
}

/**********************\
|* Internal Functions *|
\**********************/

function _approveContractCall(bytes memory params, bytes32 commandId) internal {
(
string memory sourceChain,
string memory sourceAddress,
address contractAddress,
bytes32 payloadHash,
bytes32 sourceTxHash,
uint256 sourceEventIndex
) = abi.decode(params, (string, string, address, bytes32, bytes32, uint256));
/**
* @dev This function computes the data hash that is used for signing
* @param commandType The type of command
* @param data The data that is being signed
* @return The hash of the data
*/
function _computeDataHash(CommandType commandType, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(commandType, data));
}

/**
* @dev This function verifies the proof data for a given data hash
* @param dataHash The hash of the data that was signed
* @param proof The data containing signers with signatures
* @return isLatestSigners True if the proof is signed by the latest signers
*/
function _verifyProof(bytes32 dataHash, bytes calldata proof) internal view returns (bool isLatestSigners) {
return authModule.validateProof(dataHash, proof);
}

function _isContractCallApproved(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
address contractAddress,
bytes32 payloadHash
) internal view returns (bool) {
bytes32 key = _getIsContractCallApprovedKey(
commandId,
sourceChain,
sourceAddress,
contractAddress,
payloadHash
);
_axelarAmplifierGatewayStorage().approvals[key] = true;
return _storage().approvals[key];
}

emit ContractCallApproved(
commandId,
sourceChain,
sourceAddress,
contractAddress,
payloadHash,
sourceTxHash,
sourceEventIndex
);
function _validateContractCall(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes32 payloadHash
) internal returns (bool valid) {
bytes32 key = _getIsContractCallApprovedKey(commandId, sourceChain, sourceAddress, msg.sender, payloadHash);
valid = _storage().approvals[key];

if (valid) {
delete _storage().approvals[key];

emit ContractCallExecuted(commandId);
}
}

function _transferOperatorship(bytes memory newOperatorsData) internal {
authModule.transferOperatorship(newOperatorsData);
function _approveMessage(bytes32 commandId, Message calldata message) internal {
bytes32 key = _getIsContractCallApprovedKey(
commandId,
message.sourceChain,
message.sourceAddress,
message.contractAddress,
message.payloadHash
);
_storage().approvals[key] = true;

// slither-disable-next-line reentrancy-events
emit OperatorshipTransferred(newOperatorsData);
emit ContractCallApproved(
commandId,
message.messageId,
message.sourceChain,
message.sourceAddress,
message.contractAddress,
message.payloadHash
);
}

/********************\
Expand All @@ -194,9 +245,9 @@ contract AxelarAmplifierGateway is IAxelarAmplifierGateway {

/**
* @notice Gets the specific storage location for preventing upgrade collisions
* @return slot containing the WeightedMultisigStorage struct
* @return slot containing the AxelarAmplifierGatewayStorage struct
*/
function _axelarAmplifierGatewayStorage() private pure returns (AxelarAmplifierGatewayStorage storage slot) {
function _storage() private pure returns (AxelarAmplifierGatewayStorage storage slot) {
assembly {
slot.slot := AXELAR_AMPLIFIER_GATEWAY_SLOT
}
Expand Down
Loading
Loading