Skip to content

Commit

Permalink
EVM/ Add support for per-chain transceivers
Browse files Browse the repository at this point in the history
  • Loading branch information
bruce-riley committed Sep 23, 2024
1 parent 28a6028 commit 4df3ca6
Show file tree
Hide file tree
Showing 5 changed files with 810 additions and 31 deletions.
9 changes: 9 additions & 0 deletions evm/src/NttManager/ManagerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,15 @@ abstract contract ManagerBase is
ITransceiver(_registeredTransceivers[i]).transferTransceiverOwnership(newOwner);
}
}

/// @inheritdoc IManagerBase
function enableTransceiverForChain(
address transceiver,
uint16 forChainId
) external onlyOwner {
_enableTransceiverForChain(transceiver, forChainId);
emit TransceiverEnabledForChain(transceiver, forChainId);
}

/// @inheritdoc IManagerBase
function setTransceiver(
Expand Down
18 changes: 18 additions & 0 deletions evm/src/NttManager/TransceiverRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,24 @@ abstract contract TransceiverRegistry {
uint8 index = _getTransceiverInfosStorage()[transceiver].index;
return (bitmap & uint64(1 << index)) != 0;
}

function _enableTransceiverForChain(
address transceiver,
uint16 chainId
) internal {
if (transceiver == address(0)) {
revert InvalidTransceiverZeroAddress();
}

mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage();
if (!transceiverInfos[transceiver].registered) {
revert NonRegisteredTransceiver(transceiver);
}

uint8 index = _getTransceiverInfosStorage()[transceiver].index;
mapping(uint16 => _EnabledTransceiverBitmap)storage _bitmaps = _getPerChainTransceiverBitmapStorage();
_bitmaps[chainId].bitmap |= uint64(1 << index);
}

function _setTransceiver(
address transceiver
Expand Down
20 changes: 18 additions & 2 deletions evm/src/interfaces/IManagerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ interface IManagerBase {
/// @notice Emmitted when the per-chain threshold required transceivers is changed.
/// @dev Topic0
/// 0x2a855b929b9a53c6fb5b5ed248b27e502b709c088e036a5aa17620c8fc5085a9.
/// @param chainId The chain to which the threshold applies
/// @param chainId The chain to which the threshold applies.
/// @param oldThreshold The old threshold.
/// @param threshold The new threshold.
event PerChainThresholdChanged(uint16 chainId, uint8 oldThreshold, uint8 threshold);
Expand All @@ -64,6 +64,13 @@ interface IManagerBase {
/// @param transceiversNum The current number of transceivers.
/// @param threshold The current threshold of transceivers.
event TransceiverAdded(address transceiver, uint256 transceiversNum, uint8 threshold);

/// @notice Emitted when a transceiver is enabled for a chain.
/// @dev Topic0
/// 0xf05962b5774c658e85ed80c91a75af9d66d2af2253dda480f90bce78aff5eda5.
/// @param transceiver The address of the transceiver.
/// @param chainId The chain to which the threshold applies.
event TransceiverEnabledForChain(address transceiver, uint16 chainId);

/// @notice Emitted when an transceiver is removed from the nttManager.
/// @dev Topic0
Expand Down Expand Up @@ -139,7 +146,7 @@ interface IManagerBase {

/// @notice Sets the per-chain threshold for the number of attestations required for a message
/// to be considered valid. Note that if a threshold is not specified for a chain, the default applies.
/// @param chainId The chain for which the threshold applies
/// @param chainId The chain for which the threshold applies.
/// @param threshold The new threshold.
/// @dev This method can only be executed by the `owner`.
function setThresholdPerChain(
Expand All @@ -160,6 +167,15 @@ interface IManagerBase {
function removeTransceiver(
address transceiver
) external;

/// @notice Enables the transceiver for the given chain.
/// @param transceiver The address of the transceiver.
/// @param chainId The chain for which the threshold applies.
/// @dev This method can only be executed by the `owner`.
function enableTransceiverForChain(
address transceiver,
uint16 chainId
) external;

/// @notice Checks if a message has been approved. The message should have at least
/// the minimum threshold of attestations from distinct endpoints.
Expand Down
212 changes: 183 additions & 29 deletions evm/test/IntegrationPerChainThresholds.t.sol
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import "wormhole-solidity-sdk/testing/helpers/WormholeSimulator.sol";
import "wormhole-solidity-sdk/Utils.sol";
//import "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol";

contract TestPerChainThresholds is Test, IRateLimiterEvents {
contract TestPerChainTransceivers is Test, IRateLimiterEvents {
NttManager nttManagerChain1;
NttManager nttManagerChain2;
NttManager nttManagerChain3;
Expand Down Expand Up @@ -106,11 +106,6 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents {
// Actually initialize properly now
wormholeTransceiverChain1.initialize();

nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1));
nttManagerChain1.setOutboundLimit(type(uint64).max);
nttManagerChain1.setInboundLimit(type(uint64).max, chainId2);
nttManagerChain1.setInboundLimit(type(uint64).max, chainId3);

// Create the second transceiver for chain 1.
WormholeTransceiver secondWormholeTransceiverChain1Implementation = new MockWormholeTransceiverContract(
address(nttManagerChain1),
Expand All @@ -125,7 +120,6 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents {
);

secondWormholeTransceiverChain1.initialize();
nttManagerChain1.setTransceiver(address(secondWormholeTransceiverChain1));

// Chain 2 setup
vm.chainId(chainId2);
Expand All @@ -151,10 +145,6 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents {
);
wormholeTransceiverChain2.initialize();

nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2));
nttManagerChain2.setOutboundLimit(type(uint64).max);
nttManagerChain2.setInboundLimit(type(uint64).max, chainId1);

// Register peer contracts for the nttManager and transceiver. Transceivers and nttManager each have the concept of peers here.
nttManagerChain1.setPeer(
chainId2, bytes32(uint256(uint160(address(nttManagerChain2)))), 9, type(uint64).max
Expand All @@ -177,7 +167,6 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents {
);

secondWormholeTransceiverChain2.initialize();
nttManagerChain2.setTransceiver(address(secondWormholeTransceiverChain2));

// Set peers for the transceivers
wormholeTransceiverChain1.setWormholePeer(
Expand Down Expand Up @@ -217,10 +206,6 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents {
);
wormholeTransceiverChain3.initialize();

nttManagerChain3.setTransceiver(address(wormholeTransceiverChain3));
nttManagerChain3.setOutboundLimit(type(uint64).max);
nttManagerChain3.setInboundLimit(type(uint64).max, chainId1);

// Register peer contracts for the nttManager and transceiver. Transceivers and nttManager each have the concept of peers here.
nttManagerChain1.setPeer(
chainId3, bytes32(uint256(uint160(address(nttManagerChain3)))), 9, type(uint64).max
Expand All @@ -245,10 +230,6 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents {
// Actually initialize properly now
secondWormholeTransceiverChain3.initialize();

nttManagerChain3.setTransceiver(address(secondWormholeTransceiverChain3));
nttManagerChain1.setOutboundLimit(type(uint64).max);
nttManagerChain1.setInboundLimit(type(uint64).max, chainId3);

// Set peers for the transceivers
wormholeTransceiverChain1.setWormholePeer(
chainId3, bytes32(uint256(uint160(address(wormholeTransceiverChain3))))
Expand All @@ -274,11 +255,18 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents {
secondWormholeTransceiverChain3.setWormholePeer(
chainId2, bytes32(uint256(uint160(address(secondWormholeTransceiverChain2))))
);
}

// Set the thresholds.
require(nttManagerChain1.getThreshold() != 0, "Threshold is zero with active transceivers");
// This test does a transfer between chain one and chain two where there are two transceivers.
// Since chain two uses the default threshold of one, posting a VAA from only one transceiver completes the transfer.
function test_defaultThresholds() public {
nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1));
nttManagerChain1.setTransceiver(address(secondWormholeTransceiverChain1));
nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2));
nttManagerChain2.setTransceiver(address(secondWormholeTransceiverChain2));
nttManagerChain3.setTransceiver(address(wormholeTransceiverChain3));
nttManagerChain3.setTransceiver(address(secondWormholeTransceiverChain3));

// Actually set it
nttManagerChain1.setThreshold(1);
nttManagerChain2.setThreshold(1);
nttManagerChain3.setThreshold(1);
Expand All @@ -289,11 +277,7 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents {

require(nttManagerChain1.getThreshold() == 1, "Default threshold is wrong");
require(nttManagerChain1.getThreshold(chainId3) == 2, "Threshold for chain 3 is wrong");
}

// This test does a transfer between chain one and chain two.
// Since chain two uses the default threshold, posting a VAA from only one transceiver completes the transfer.
function test_defaultThreshold() public {
vm.chainId(chainId1);

// Setting up the transfer
Expand Down Expand Up @@ -417,9 +401,27 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents {
}
}

// This test does a transfer between chain one and chain three.
// Since the threshold for these two chains is two, the transfer is not completed until both VAAs are posted.
// This test does a transfer between chain one and chain three where there are two transceivers and the per-chain threshold is two.
// The transfer is not completed until both VAAs are posted.
function test_perChainThreshold() public {
nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1));
nttManagerChain1.setTransceiver(address(secondWormholeTransceiverChain1));
nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2));
nttManagerChain2.setTransceiver(address(secondWormholeTransceiverChain2));
nttManagerChain3.setTransceiver(address(wormholeTransceiverChain3));
nttManagerChain3.setTransceiver(address(secondWormholeTransceiverChain3));

nttManagerChain1.setThreshold(1);
nttManagerChain2.setThreshold(1);
nttManagerChain3.setThreshold(1);

// On chain 1, set the threshold to chain 3 to be different.
nttManagerChain1.setThresholdPerChain(chainId3, 2);
nttManagerChain3.setThresholdPerChain(chainId1, 2);

require(nttManagerChain1.getThreshold() == 1, "Default threshold is wrong");
require(nttManagerChain1.getThreshold(chainId3) == 2, "Threshold for chain 3 is wrong");

vm.chainId(chainId1);

// Setting up the transfer
Expand Down Expand Up @@ -545,6 +547,158 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents {
require(token1.balanceOf(userD) == sendingAmount, "User received funds");
}

// This test does a transfer between chain one and chain three where there there is only one transceiver and the per-chain threshold is one.
// The transfer is not completed until both VAAs are posted.
function test_perChainTransceiver() public {
// By default, there are two transceivers and a threshold of two.
nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1));
nttManagerChain1.setTransceiver(address(secondWormholeTransceiverChain1));
nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2));
nttManagerChain2.setTransceiver(address(secondWormholeTransceiverChain2));
nttManagerChain3.setTransceiver(address(wormholeTransceiverChain3));
nttManagerChain3.setTransceiver(address(secondWormholeTransceiverChain3));

nttManagerChain1.setThreshold(2);
nttManagerChain2.setThreshold(2);
nttManagerChain3.setThreshold(2);

// Between chains one and three, there is only a single transceiver with a threshold of one.
nttManagerChain1.enableTransceiverForChain(address(wormholeTransceiverChain1), chainId3);
nttManagerChain3.enableTransceiverForChain(address(wormholeTransceiverChain3), chainId1);
nttManagerChain1.setThresholdPerChain(chainId3, 1);
nttManagerChain3.setThresholdPerChain(chainId1, 1);

require(nttManagerChain1.getThreshold() == 2, "Default threshold is wrong on chain 1");
require(nttManagerChain1.getThreshold(chainId3) == 1, "Threshold for chain 3 is wrong on chain 1");
require(nttManagerChain1.getThreshold() == 2, "Default threshold is wrong on chain 3");
require(nttManagerChain3.getThreshold(chainId1) == 1, "Threshold for chain 1 is wrong on chain 3");

vm.chainId(chainId1);

// Setting up the transfer
DummyToken token1 = DummyToken(nttManagerChain1.token());
DummyToken token3 = DummyTokenMintAndBurn(nttManagerChain3.token());

uint8 decimals = token1.decimals();
uint256 sendingAmount = 5 * 10 ** decimals;
token1.mintDummy(address(userA), 5 * 10 ** decimals);
vm.startPrank(userA);
token1.approve(address(nttManagerChain1), sendingAmount);

vm.recordLogs();

// Send token from chain 1 to chain 3, userB.
{
uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1));
uint256 userBalanceBefore = token1.balanceOf(address(userA));
nttManagerChain1.transfer(sendingAmount, chainId3, bytes32(uint256(uint160(userB))));

// Balance check on funds going in and out working as expected
uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain1));
uint256 userBalanceAfter = token1.balanceOf(address(userB));
require(
nttManagerBalanceBefore + sendingAmount == nttManagerBalanceAfter,
"Should be locking the tokens"
);
require(
userBalanceBefore - sendingAmount == userBalanceAfter,
"User should have sent tokens"
);
}

vm.stopPrank();

// Get and sign the log to go down the other pipes. There should be two messages since we have two transceivers.
Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs());
require(2 == entries.length, "Unexpected number of log entries 3");
bytes[] memory encodedVMs = new bytes[](entries.length);
for (uint256 i = 0; i < encodedVMs.length; i++) {
encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1);
}

// Chain3 verification and checks
vm.chainId(chainId3);

uint256 supplyBefore = token3.totalSupply();
/*
// Submit the first message on chain 3. The numbers shouldn't change yet since the threshold is two.
wormholeTransceiverChain3.receiveMessage(encodedVMs[0]);
uint256 supplyAfter = token3.totalSupply();
require(supplyBefore == supplyAfter, "Supplies changed early");
require(token3.balanceOf(userB) == 0, "User receive tokens early");
require(token3.balanceOf(address(nttManagerChain3)) == 0, "NttManager has unintended funds");
// Submit the second message and the transfer should complete.
secondWormholeTransceiverChain3.receiveMessage(encodedVMs[1]);
supplyAfter = token3.totalSupply();
require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match");
require(token3.balanceOf(userB) == sendingAmount, "User didn't receive tokens");
require(token3.balanceOf(address(nttManagerChain3)) == 0, "NttManager has unintended funds");
// Go back the other way from a THIRD user
vm.prank(userB);
token3.transfer(userC, sendingAmount);
vm.startPrank(userC);
token3.approve(address(nttManagerChain3), sendingAmount);
vm.recordLogs();
// Supply checks on the transfer
supplyBefore = token3.totalSupply();
nttManagerChain3.transfer(
sendingAmount,
chainId1,
toWormholeFormat(userD),
toWormholeFormat(userC),
false,
encodeTransceiverInstruction(true)
);
supplyAfter = token3.totalSupply();
require(sendingAmount - supplyBefore == supplyAfter, "Supplies don't match");
require(token3.balanceOf(userB) == 0, "OG user receive tokens");
require(token3.balanceOf(userC) == 0, "Sending user didn't receive tokens");
require(
token3.balanceOf(address(nttManagerChain3)) == 0,
"NttManager didn't receive unintended funds"
);
// Get and sign the log to go down the other pipe. Thank you to whoever wrote this code in the past!
entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs());
require(2 == entries.length, "Unexpected number of log entries for response");
encodedVMs = new bytes[](entries.length);
for (uint256 i = 0; i < encodedVMs.length; i++) {
encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId3);
}
// Chain1 verification and checks with the receiving of the message
vm.chainId(chainId1);
// Submit the first message back on chain one. Nothing should happen because our threshold is two.
supplyBefore = token1.totalSupply();
wormholeTransceiverChain1.receiveMessage(encodedVMs[0]);
supplyAfter = token1.totalSupply();
require(supplyBefore == supplyAfter, "Supplies don't match between operations");
require(token1.balanceOf(userB) == 0, "OG user receive tokens");
require(token1.balanceOf(userC) == 0, "Sending user didn't receive tokens");
require(token1.balanceOf(userD) == 0, "User received funds before they should");
// Submit the second message back on chain one. This should update the balance.
supplyBefore = token1.totalSupply();
secondWormholeTransceiverChain1.receiveMessage(encodedVMs[1]);
supplyAfter = token1.totalSupply();
require(supplyBefore == supplyAfter, "Supplies don't match between operations"); /////////////////// Is this right??
require(token1.balanceOf(userB) == 0, "OG user receive tokens");
require(token1.balanceOf(userC) == 0, "Sending user didn't receive tokens");
require(token1.balanceOf(userD) == sendingAmount, "User received funds");
*/
}

function encodeTransceiverInstruction(bool relayer_off) public view returns (bytes memory) {
WormholeTransceiver.WormholeTransceiverInstruction memory instruction =
IWormholeTransceiver.WormholeTransceiverInstruction(relayer_off);
Expand Down
Loading

0 comments on commit 4df3ca6

Please sign in to comment.