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: update axelar transceiver #21

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2cbc14d
add axelar tranceiver
Foivos May 17, 2024
482c523
Added the rest of the files
Foivos May 17, 2024
164581d
added a proxy as well
Foivos May 23, 2024
e80074b
trying to add some tests
Foivos May 23, 2024
0307a6f
forge install: axelar-cgp-solidity
Foivos May 23, 2024
32be090
added some tests
Foivos May 24, 2024
10f3c25
Rebase
Foivos May 30, 2024
77d45c9
remove the proxy
Foivos May 30, 2024
656c3fd
using axelar gmp executable
Foivos May 30, 2024
2a42105
Fix CI and format
nik-suri Jun 4, 2024
99c8c03
added a deployment script
Foivos Jun 5, 2024
644a0d2
update deployment script
Foivos Jun 6, 2024
0259e69
add readme and rename dir
Foivos Jun 6, 2024
25840ce
Added a receiving token test
Foivos Jun 8, 2024
274a2e1
added a fail test if the address is not trusted.
Foivos Jun 8, 2024
ac58ef5
remove unused var
Foivos Jun 10, 2024
f933e07
run forge fmt
Foivos Jun 10, 2024
c93d61e
tests now prperly check for reverts
Foivos Jun 11, 2024
ff2bf33
Add event to setAxelarChainId
Foivos Jun 19, 2024
9041b8a
Add validation for setAxelarChainId
Foivos Jun 19, 2024
daaf70d
Added better tests
Foivos Jun 19, 2024
3e9842a
run fmt
Foivos Jun 19, 2024
57b2949
Update src/axelar/AxelarTransceiver.sol
canhtrinh Jun 24, 2024
20c52cb
Update test/axelar/AxelarTransceiver.t.sol
canhtrinh Jun 24, 2024
7227a24
Update test/axelar/AxelarTransceiver.t.sol
canhtrinh Jun 24, 2024
03842fb
restrict use of setAxelarChainId
Foivos Jun 25, 2024
f2aa572
remove confusing comment
Foivos Jun 26, 2024
29dc3f5
fix import in test
Foivos Jun 27, 2024
6798c0f
forge fmt
Foivos Jun 27, 2024
d900a54
try a different import
Foivos Jun 27, 2024
d75afc6
trying to fix CLI
Foivos Jun 27, 2024
8422c51
trying more stuff
Foivos Jun 27, 2024
1668980
sqush commits
Foivos Jun 28, 2024
7645971
try different import
Foivos Jun 27, 2024
680ad07
differnet import again
Foivos Jun 27, 2024
5120f43
fix end to end tests
Foivos Jun 27, 2024
e38bb8f
forge fmt
Foivos Jun 27, 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
2 changes: 1 addition & 1 deletion lib/example-native-token-transfers
9 changes: 9 additions & 0 deletions src/axelar/AxelarTransceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,20 @@ 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);
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
118 changes: 118 additions & 0 deletions src/token/wstETHL2Token.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;

import {OwnableUpgradeable} from
"openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
import {ERC20Upgradeable} from
"openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
import {ERC20BurnableUpgradeable} from
"openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import {UUPSUpgradeable} from
"openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
import {INttToken} from "@wormhole-foundation/native_token_transfer/interfaces/INttToken.sol";

/// @title WstEthL2Token
/// @notice A UUPS upgradeable token with access controlled minting and burning.
contract WstEthL2Token is
INttToken,
UUPSUpgradeable,
ERC20Upgradeable,
ERC20BurnableUpgradeable,
OwnableUpgradeable
{
// =============== Storage ==============================================================

struct MinterStorage {
address _minter;
}

bytes32 private constant MINTER_SLOT = bytes32(uint256(keccak256("wsteth.minter")) - 1);

// =============== Storage Getters/Setters ==============================================

function _getMinterStorage() internal pure returns (MinterStorage storage $) {
uint256 slot = uint256(MINTER_SLOT);
assembly ("memory-safe") {
$.slot := slot
}
}

/// @notice A function to set the new minter for the tokens.
/// @param newMinter The address to add as both a minter and burner.
function setMinter(address newMinter) external onlyOwner {
if (newMinter == address(0)) {
revert InvalidMinterZeroAddress();
}
address previousMinter = _getMinterStorage()._minter;
_getMinterStorage()._minter = newMinter;
emit NewMinter(previousMinter, newMinter);
}

/// @dev Returns the address of the current minter.
function minter() public view returns (address) {
MinterStorage storage $ = _getMinterStorage();
return $._minter;
}

/// @dev Throws if called by any account other than the minter.
modifier onlyMinter() {
if (minter() != _msgSender()) {
revert CallerNotMinter(_msgSender());
}
_;
}

/// @dev An error thrown when a method is not implemented.
error UnimplementedMethod();

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @notice A one-time configuration method meant to be called immediately upon the deployment of `WstEthL2Token`. It sets
/// up the token's name, symbol, and owner
function initialize(
string memory _name,
string memory _symbol,
address _owner
) external initializer {
// OpenZeppelin upgradeable contracts documentation says:
//
// "Use with multiple inheritance requires special care. Initializer
// functions are not linearized by the compiler like constructors.
// Because of this, each __{ContractName}_init function embeds the
// linearized calls to all parent initializers. As a consequence,
// calling two of these init functions can potentially initialize the
// same contract twice."
//
// Note that ERC20 extensions do not linearize calls to ERC20Upgradeable
// initializer so we call all extension initializers individually.
__ERC20_init(_name, _symbol);
__Ownable_init(_owner);

// These initializers don't do anything, so we won't call them
// __ERC20Burnable_init();
// __UUPSUpgradeable_init();
}

/// @notice A function that will burn tokens held by the `msg.sender`.
/// @param _value The amount of tokens to be burned.
function burn(uint256 _value) public override(INttToken, ERC20BurnableUpgradeable) onlyMinter {
ERC20BurnableUpgradeable.burn(_value);
}

/// @notice This method is not implemented and should not be called.
function burnFrom(address, uint256) public pure override {
revert UnimplementedMethod();
}

/// @notice A function that mints new tokens to a specific account.
/// @param _account The address where new tokens will be minted.
/// @param _amount The amount of new tokens that will be minted.
function mint(address _account, uint256 _amount) external onlyMinter {
_mint(_account, _amount);
}

function _authorizeUpgrade(address /* newImplementation */ ) internal view override onlyOwner {}
}
74 changes: 64 additions & 10 deletions test/axelar/AxelarTransceiver.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity >=0.8.8 <0.9.0;

import "../../src/axelar/AxelarTransceiver.sol";
import "../../src/token/wstETHL2Token.sol";
import "./mock/MockGateway.sol";
import {MockAxelarGasService} from "./mock/MockGasService.sol";
import {TransceiverStructs} from
Expand All @@ -19,11 +20,24 @@ 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;
bool constant SKIP_RATE_LIMITING = true;

uint64 constant RATE_LIMIT_DURATION = 0;
bool constant SKIP_RATE_LIMITING = true;

uint256 constant DEVNET_GUARDIAN_PK =
0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0;

Expand All @@ -34,9 +48,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 +86,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);
}

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);
/*assertEq(transceiver.idToAxelarChainIds(chainId), chainName);
assertEq(transceiver.axelarChainIdToId(chainName),chainId);
assertEq(transceiver.idToAxelarAddress(chainId), axelarAddress);
assertEq(transceiver.axelarAddressToId(axelarAddress), chainId);*/

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

function test_setAxelarChainIdNotOwner() public {
Expand All @@ -101,11 +131,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 +204,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 +222,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 +239,37 @@ 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(messageId, chainName, axelarAddress, payload);
}

function testFail_executeNotTrustedAddress() public {
string memory chainName = "chainName";
string memory axelarAddress = "axelarAddress";
bytes memory payload = bytes("");
vm.expectRevert(abi.encodeWithSignature('InvalidSibling(uint16,string,string)', 0, chainName, axelarAddress));
transceiver.execute(bytes32(0), chainName, axelarAddress, payload);
}
}
Loading
Loading