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

fix: address axelar transceiver audit comments #22

Merged
merged 4 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
11 changes: 11 additions & 0 deletions src/axelar/AxelarTransceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,22 @@ contract AxelarTransceiver is IAxelarTransceiver, AxelarGMPExecutable, Transceiv
string calldata chainName,
string calldata transceiverAddress
) external virtual onlyOwner {
if (chainId == 0 || bytes(chainName).length == 0 || bytes(transceiverAddress).length == 0) {
revert InvalidChainIdParams();
}

AxelarTransceiverStorage storage slot = _storage();

if (bytes(slot.idToAxelarChainId[chainId]).length != 0) revert ChainIdAlreadySet(chainId);

if (slot.axelarChainIdToId[chainName] != 0) revert AxelarChainIdAlreadySet(chainName);
milapsheth marked this conversation as resolved.
Show resolved Hide resolved

slot.idToAxelarChainId[chainId] = chainName;
slot.axelarChainIdToId[chainName] = chainId;
slot.idToTransceiverAddress[chainId] = transceiverAddress;
slot.transceiverAddressToId[transceiverAddress] = chainId;

emit AxelarChainIdSet(chainId, chainName, transceiverAddress);
}

/// @notice Fetch the delivery price for a given recipient chain transfer.
Expand Down
19 changes: 18 additions & 1 deletion src/axelar/interfaces/IAxelarTransceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@ interface IAxelarTransceiver is ITransceiver {

/// @notice Chain Id passed is not valid.
/// @param chainId The wormhole chainId.
error InvalidChainId(uint16 chainId, string chainName, string destinationContract);
/// @param chainName The axelar chainName.
/// @param transceiverAddress The address of the Transceiver as a string.
error InvalidChainId(uint16 chainId, string chainName, string transceiverAddress);

/// @notice Chain Id passed is zero, or Axelar Chain Id or Transceiver Address were empty.
error InvalidChainIdParams();

/// @notice Chain Id is already being used.
error ChainIdAlreadySet(uint16 chainId);

/// @notice Axelar chain Id is already being used.
error AxelarChainIdAlreadySet(string axelarChainId);

/// @notice Emmited when a transceiver message is sent.
/// @param recipientChainId The wormhole chainId of the destination chain.
Expand All @@ -26,6 +37,12 @@ interface IAxelarTransceiver is ITransceiver {
bytes32 indexed refundAddress
);

/// @notice Emmited when the chain id is set.
/// @param chainId The wormhole chainId of the destination chain.
/// @param chainName The axelar chain name.
/// @param transceiverAddress The transceiver address as a string.
event AxelarChainIdSet(uint16 chainId, string chainName, string transceiverAddress);

/**
* Set the bridge manager contract address
* @param chainId The chainId of the chain. This is used to identify the chain in the EndpointManager.
Expand Down
64 changes: 53 additions & 11 deletions test/axelar/AxelarTransceiver.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ import "forge-std/Test.sol";
import "openzeppelin-contracts/contracts/access/Ownable.sol";

contract AxelarTransceiverTest is Test {
event ContractCall(
address indexed sender,
string destinationChain,
string destinationContractAddress,
bytes32 indexed payloadHash,
bytes payload
);

event AxelarChainIdSet(uint16 chainId, string chainName, string transceiverAddress);

address constant OWNER = address(1004);

uint64 constant RATE_LIMIT_DURATION = 0;
Expand All @@ -34,9 +44,6 @@ contract AxelarTransceiverTest is Test {
WstEthL2TokenHarness token;

function setUp() public {
string memory url = "https://ethereum-sepolia-rpc.publicnode.com";
vm.createSelectFork(url);

gateway = IAxelarGateway(new MockAxelarGateway());
gasService = IAxelarGasService(address(new MockAxelarGasService()));

Expand Down Expand Up @@ -75,12 +82,31 @@ contract AxelarTransceiverTest is Test {
string memory chainName = "chainName";
string memory axelarAddress = "axelarAddress";

vm.expectEmit(address(transceiver));
emit AxelarChainIdSet(chainId, chainName, axelarAddress);

vm.prank(OWNER);
transceiver.setAxelarChainId(chainId, chainName, axelarAddress);
/*assertEq(transceiver.idToAxelarChainIds(chainId), chainName);
assertEq(transceiver.axelarChainIdToId(chainName),chainId);
assertEq(transceiver.idToAxelarAddress(chainId), axelarAddress);
assertEq(transceiver.axelarAddressToId(axelarAddress), chainId);*/
}

function test_setAxelarChainIdDuplicateChainId() public {
uint16 chainId = 1;
string memory chainName = "chainName";
string memory axelarAddress = "axelarAddress";

vm.expectEmit(address(transceiver));
emit AxelarChainIdSet(chainId, chainName, axelarAddress);

vm.prank(OWNER);
transceiver.setAxelarChainId(chainId, chainName, axelarAddress);

vm.prank(OWNER);
vm.expectRevert(abi.encodeWithSignature("ChainIdAlreadySet(uint16)", chainId));
transceiver.setAxelarChainId(chainId, chainName, axelarAddress);

vm.prank(OWNER);
vm.expectRevert(abi.encodeWithSignature("AxelarChainIdAlreadySet(string)", chainName));
transceiver.setAxelarChainId(chainId + 1, chainName, axelarAddress);
}

function test_setAxelarChainIdNotOwner() public {
Expand All @@ -101,11 +127,18 @@ contract AxelarTransceiverTest is Test {
bytes32 recipientNttManagerAddress = bytes32(uint256(1010));
bytes memory nttManagerMessage = bytes("nttManagerMessage");
bytes32 refundAddress = bytes32(uint256(1011));
bytes memory payload = abi.encode(manager, nttManagerMessage, recipientNttManagerAddress);
TransceiverStructs.TransceiverInstruction memory instruction =
TransceiverStructs.TransceiverInstruction(0, bytes(""));

vm.prank(OWNER);
transceiver.setAxelarChainId(chainId, chainName, axelarAddress);

vm.expectEmit(address(gateway));
emit ContractCall(
address(transceiver), chainName, axelarAddress, keccak256(payload), payload
);

vm.prank(address(manager));
transceiver.sendMessage(
chainId, instruction, nttManagerMessage, recipientNttManagerAddress, refundAddress
Expand Down Expand Up @@ -167,6 +200,7 @@ contract AxelarTransceiverTest is Test {
uint16 chainId = 2;
string memory chainName = "chainName";
string memory axelarAddress = "axelarAddress";
bytes32 messageId = bytes32(uint256(25));
bytes32 recipientNttManagerAddress = bytes32(uint256(uint160(address(manager))));

bytes32 to = bytes32(uint256(1234));
Expand All @@ -184,9 +218,9 @@ contract AxelarTransceiverTest is Test {
bytes memory nttManagerMessage;
{
uint16 length = uint16(nttPayload.length);
bytes32 messageId = bytes32(uint256(0));
bytes32 nttMessageId = bytes32(uint256(0));
bytes32 sender = bytes32(uint256(1));
nttManagerMessage = abi.encodePacked(messageId, sender, length, nttPayload);
nttManagerMessage = abi.encodePacked(nttMessageId, sender, length, nttPayload);
}

bytes32 sourceNttManagerAddress = bytes32(uint256(1012));
Expand All @@ -201,21 +235,29 @@ contract AxelarTransceiverTest is Test {
token.setMinter(OWNER);
vm.prank(OWNER);
token.mint(address(manager), amount);
gateway.approveContractCall(messageId, chainName, axelarAddress, keccak256(payload));

transceiver.execute(bytes32(0), chainName, axelarAddress, payload);
transceiver.execute(messageId, chainName, axelarAddress, payload);

if (token.balanceOf(fromWormholeFormat(to)) != amount) revert("Amount Incorrect");

vm.prank(OWNER);
token.mint(address(manager), amount);
vm.expectRevert(abi.encodeWithSignature("NotApprovedByGateway()"));
transceiver.execute(bytes32(0), chainName, axelarAddress, payload);
}

function test_executeNotTrustedAddress() public {
string memory chainName = "chainName";
string memory axelarAddress = "axelarAddress";
bytes memory payload = bytes("");
bytes32 messageId = keccak256(bytes("message Id"));
gateway.approveContractCall(messageId, chainName, axelarAddress, keccak256(payload));
vm.expectRevert(
abi.encodeWithSignature(
"InvalidSibling(uint16,string,string)", 0, chainName, axelarAddress
)
);
transceiver.execute(bytes32(0), chainName, axelarAddress, payload);
transceiver.execute(messageId, chainName, axelarAddress, payload);
}
}
199 changes: 199 additions & 0 deletions test/axelar/AxelarTransceiverEndToEnd.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;

import "../../src/axelar/AxelarTransceiver.sol";
import "./mock/MockGateway.sol";
import {MockAxelarGasService} from "./mock/MockGasService.sol";
import {TransceiverStructs} from
"@wormhole-foundation/native_token_transfer/libraries/TransceiverStructs.sol";
import {NttManager} from "@wormhole-foundation/native_token_transfer/NttManager/NttManager.sol";
import {INttManager} from "@wormhole-foundation/native_token_transfer/interfaces/INttManager.sol";
import {IManagerBase} from "@wormhole-foundation/native_token_transfer/interfaces/IManagerBase.sol";
import {ERC1967Proxy} from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {WstEthL2Token} from "src/token/WstEthL2Token.sol";

import "forge-std/console.sol";
import "forge-std/Test.sol";
import "openzeppelin-contracts/contracts/access/Ownable.sol";

contract AxelarTransceiverEndToEnd is Test {
address constant OWNER = address(1004);
uint64 constant RATE_LIMIT_DURATION = 0;
bool constant SKIP_RATE_LIMITING = true;

uint256 constant DEVNET_GUARDIAN_PK =
0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0;

AxelarTransceiver sourceTransceiver;
IAxelarGateway gateway;
IAxelarGasService gasService;
NttManager sourceNttmanager;
WstEthL2Token sourceToken;
uint16 sourceChainId;

AxelarTransceiver recipientTransceiver;
NttManager recipientNttManager;
WstEthL2Token recipientToken;
uint16 recipientChainId;

function setUp() public {
gateway = IAxelarGateway(new MockAxelarGateway());
gasService = IAxelarGasService(address(new MockAxelarGasService()));
// Setup Source Infrastructure
sourceChainId = 1;
address tokenImplementaion = address(new WstEthL2Token());
sourceToken = WstEthL2Token(
address(
new ERC1967Proxy(
tokenImplementaion,
abi.encodeWithSelector(
WstEthL2Token.initialize.selector, "Source Token", "ST", OWNER
)
)
)
);
vm.prank(OWNER);
sourceToken.setMinter(OWNER);
address sourceManagerImplementation = address(
new NttManager(
address(sourceToken),
IManagerBase.Mode.LOCKING,
sourceChainId,
RATE_LIMIT_DURATION,
SKIP_RATE_LIMITING
)
);
sourceNttmanager = NttManager(address(new ERC1967Proxy(sourceManagerImplementation, "")));
sourceNttmanager.initialize();
sourceNttmanager.transferOwnership(OWNER);
address srcTransceiverImplementation = address(
new AxelarTransceiver(address(gateway), address(gasService), address(sourceNttmanager))
);
sourceTransceiver =
AxelarTransceiver(address(new ERC1967Proxy(srcTransceiverImplementation, "")));
sourceTransceiver.initialize();
vm.prank(OWNER);
sourceNttmanager.setTransceiver(address(sourceTransceiver));

// Setup Recipient Infrastructure
recipientChainId = 2;
recipientToken = WstEthL2Token(
address(
new ERC1967Proxy(
tokenImplementaion,
abi.encodeWithSelector(
WstEthL2Token.initialize.selector, "Source Token", "ST", OWNER
)
)
)
);
vm.prank(OWNER);
recipientToken.setMinter(OWNER);
address recipientManagerImplementation = address(
new NttManager(
address(recipientToken),
IManagerBase.Mode.LOCKING,
recipientChainId,
RATE_LIMIT_DURATION,
SKIP_RATE_LIMITING
)
);
recipientNttManager =
NttManager(address(new ERC1967Proxy(recipientManagerImplementation, "")));
recipientNttManager.initialize();
recipientNttManager.transferOwnership(OWNER);
address rcptTransceiverImplementation = address(
new AxelarTransceiver(
address(gateway), address(gasService), address(recipientNttManager)
)
);
recipientTransceiver =
AxelarTransceiver(address(new ERC1967Proxy(rcptTransceiverImplementation, "")));
recipientTransceiver.initialize();
vm.prank(OWNER);
recipientNttManager.setTransceiver(address(recipientTransceiver));

bytes32 sourceNttManagerAddress = bytes32(uint256(uint160(address(sourceNttmanager))));
bytes32 recipientNttManagerAddress = bytes32(uint256(uint160(address(recipientNttManager))));

// set peer ntt manager on source
vm.prank(OWNER);
sourceNttmanager.setPeer(recipientChainId, recipientNttManagerAddress, 18, 100000000);

// set peer ntt manager on recipient
vm.prank(OWNER);
recipientNttManager.setPeer(sourceChainId, sourceNttManagerAddress, 18, 100000000);

string memory sourceChainName = "srcChain";
string memory sourceAxelarAddress = "srcAxelar";

string memory recipientChainName = "recipientChain";
string memory recipientAxelarAddress = "recipientAxelar";

vm.prank(OWNER);
sourceTransceiver.setAxelarChainId(
recipientChainId, recipientChainName, recipientAxelarAddress
);

vm.prank(OWNER);
recipientTransceiver.setAxelarChainId(sourceChainId, sourceChainName, sourceAxelarAddress);

// token mint source
vm.prank(OWNER);
sourceToken.mint(address(sourceNttmanager), 10e6 ether);

vm.prank(OWNER);
recipientToken.mint(address(recipientNttManager), 10e6 ether);
}

function testAxelarTransceiverEndToEnd() public {
bytes32 refundAddress = bytes32(uint256(1011));
TransceiverStructs.TransceiverInstruction memory instruction =
TransceiverStructs.TransceiverInstruction(0, bytes(""));

// SEND MESSAGE ON SOURCE CHAIN
bytes32 to = bytes32(uint256(1234));
uint64 amount = 12345670000000000;
bytes memory nttPayload;
{
bytes4 prefix = TransceiverStructs.NTT_PREFIX;
uint8 decimals = 18;
bytes32 srcToken = bytes32(uint256(uint160(address(sourceToken))));
uint16 toChain = 2;
nttPayload = abi.encodePacked(prefix, decimals, amount, srcToken, to, toChain);
}

bytes memory nttManagerMessage;
{
uint16 length = uint16(nttPayload.length);
bytes32 nttMessageId = bytes32(uint256(0));
bytes32 sender = bytes32(uint256(1));
nttManagerMessage = abi.encodePacked(nttMessageId, sender, length, nttPayload);
}
bytes32 sourceNttManagerAddress = bytes32(uint256(uint160(address(sourceNttmanager))));
bytes32 recipientNttManagerAddress = bytes32(uint256(uint160(address(recipientNttManager))));

vm.prank(address(sourceNttmanager));
sourceTransceiver.sendMessage(
recipientChainId,
instruction,
nttManagerMessage,
recipientNttManagerAddress,
refundAddress
);

// EXECUTE ON RECIPIENT CHAIN
bytes memory payload =
abi.encode(sourceNttManagerAddress, nttManagerMessage, recipientNttManagerAddress);

string memory sourceChainName = "srcChain";
string memory sourceAxelarAddress = "srcAxelar";
bytes32 messageId = keccak256(bytes("message Id"));

gateway.approveContractCall(
messageId, sourceChainName, sourceAxelarAddress, keccak256(payload)
);
recipientTransceiver.execute(messageId, sourceChainName, sourceAxelarAddress, payload);
if (recipientToken.balanceOf(fromWormholeFormat(to)) != amount) revert("Amount Incorrect");
}
}
Loading