Skip to content

Commit

Permalink
finish wormhole, write in a bunch of the tests
Browse files Browse the repository at this point in the history
probably still a lot of bugs but can probably push up a testnet
deployment just to see if everything is gluing together ok
  • Loading branch information
dbeal-eth committed Oct 1, 2023
1 parent 843d554 commit 6613846
Show file tree
Hide file tree
Showing 19 changed files with 554 additions and 278 deletions.
24 changes: 24 additions & 0 deletions protocol/synthetix/cannonfile.test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ artifact = "contracts/generated/test/TestableVaultEpochStorage.sol:TestableVault
[contract.TestableVaultStorage]
artifact = "contracts/generated/test/TestableVaultStorage.sol:TestableVaultStorage"

[contract.CallSelfModule]
artifact = "contracts/mocks/CallSelfModule.sol:CallSelfModule"

[contract.FakeSendCrossChainModule]
artifact = "contracts/mocks/FakeSendCrossChainModule.sol:FakeSendCrossChainModule"

# Core
[router.CoreRouter]
contracts = [
Expand All @@ -89,6 +95,8 @@ contracts = [
"RewardsManagerModule",
"UtilsModule",
"VaultModule",
"CallSelfModule",
"FakeSendCrossChainModule",
"TestableAccountStorage",
"TestableAccountRBACStorage",
"TestableCollateralStorage",
Expand Down Expand Up @@ -133,3 +141,19 @@ args = [
"0x7d632bd2<%= defaultAbiCoder.encode(['bytes32', 'bool'], [formatBytes32String('createPool'), true]).slice(2) %>",
]
]

# cross chain wormhole
[import.wormhole]
source = "erc7412-wormhole:latest"

[invoke.configureWormholeCrossChain]
target = ["CoreProxy"]
fromCall.func = "owner"
func = "configureWormholeCrossChain"
args = [
"<%= imports.wormhole.imports.wormhole.contracts.Wormhole.address %>",
"<%= imports.wormhole.imports.wormhole.contracts.Wormhole.address %>",
"<%= imports.wormhole.contracts.WormholeERC7412Receiver.address %>",
[1, 13370],
[1, 13370]
]
3 changes: 3 additions & 0 deletions protocol/synthetix/cannonfile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ args = [
"0x7d632bd2<%= defaultAbiCoder.encode(['bytes32', 'bool'], [formatBytes32String('withdrawMarketUsd'), true]).slice(2) %>",
"0x7d632bd2<%= defaultAbiCoder.encode(['bytes32', 'bool'], [formatBytes32String('claimRewards'), true]).slice(2) %>",
"0x7d632bd2<%= defaultAbiCoder.encode(['bytes32', 'bool'], [formatBytes32String('delegateCollateral'), true]).slice(2) %>",
"0x7d632bd2<%= defaultAbiCoder.encode(['bytes32', 'bool'], [formatBytes32String('createCrossChainPool'), true]).slice(2) %>",
"0x7d632bd2<%= defaultAbiCoder.encode(['bytes32', 'bool'], [formatBytes32String('setCrossChainPoolSelectors'), true]).slice(2) %>",
"0x7d632bd2<%= defaultAbiCoder.encode(['bytes32', 'bool'], [formatBytes32String('setCrossChainPoolConfiguration'), true]).slice(2) %>",
]
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ interface ICrossChainPoolModule {
uint128 thisChainPoolId
);

function setCrossChainPoolSelectors(
uint128 poolId,
bytes4 readSelector,
bytes4 writeSelector
) external;

function createCrossChainPool(
uint128 sourcePoolId,
uint64 targetChainId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity >=0.8.11 <0.9.0;

import "./external/IWormholeRelayer.sol";
import "./external/IWormholeCrossChainRead.sol";
import "./external/IWormholeERC7412Receiver.sol";

/**
* @title Module with assorted utility functions.
Expand All @@ -18,12 +18,24 @@ interface IOffchainWormholeModule {
* @param supportedNetworks The list of supported chain ids for the cross chain protocol
* @param selectors Mapping of selectors used for wormhole related to the supportedNetworks
*/

function configureWormholeCrossChain(
IWormholeRelayerSend send,
IWormholeRelayerDelivery recv,
IWormholeCrossChainRead read,
IWormholeERC7412Receiver read,
uint64[] memory supportedNetworks,
uint16[] memory selectors
) external;

function readCrossChainWormhole(
bytes32 /* subscriptionId */,
uint64[] memory chains,
bytes memory call,
uint256 /* gasLimit */
) external returns (bytes[] memory responses);

function sendWormholeMessage(
uint64[] memory chainIds,
bytes memory message,
uint256 gasLimit
) external returns (bytes32[] memory sequenceNumbers);
}
11 changes: 11 additions & 0 deletions protocol/synthetix/contracts/interfaces/external/IERC7412.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

interface IERC7412 {
error FeeRequired(uint amount);
error OracleDataRequired(address oracleContract, bytes oracleQuery);

function oracleId() external view returns (bytes32 oracleId);

function fulfillOracleQuery(bytes calldata signedOffchainData) external payable;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

import "@synthetixio/core-contracts/contracts/interfaces/IERC20.sol";
import "./IERC7412.sol";

interface IWormholeERC7412Receiver is IERC7412 {
struct CrossChainRequest {
uint64 chainSelector;
uint256 timestamp;
address target;
bytes data;
}

function getCrossChainData(
CrossChainRequest[] memory reqs,
uint256 maxAge
) external view returns (bytes[] memory);
}
15 changes: 15 additions & 0 deletions protocol/synthetix/contracts/mocks/CallSelfModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

import "@synthetixio/core-contracts/contracts/utils/CallUtil.sol";

/**
* A module which can be added to the core system to allow it to call itself. Used for cross chain purposes, which rely on messages to self
*/
contract CallSelfModule {
using CallUtil for address;

function callSelf(bytes memory selfCallData) external returns (bytes memory) {
return address(this).tryCall(selfCallData);
}
}
17 changes: 17 additions & 0 deletions protocol/synthetix/contracts/mocks/FakeSendCrossChainModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

import "@synthetixio/core-contracts/contracts/utils/CallUtil.sol";

/**
* A module which can be added to the core system to allow it to call itself. Used for cross chain purposes, which rely on messages to self
*/
contract FakeSendCrossChainModule {
function sendCrossChainFake(
uint64[] memory targetChains,
bytes memory data,
uint256 gasLimit
) external returns (bytes32[] memory) {
return new bytes32[](targetChains.length);
}
}
37 changes: 37 additions & 0 deletions protocol/synthetix/contracts/mocks/FakeWormholeCrossChainRead.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

import "../interfaces/external/IWormholeERC7412Receiver.sol";

contract FakeWormholeCrossChainRead is IWormholeERC7412Receiver {
mapping(bytes32 => bytes) public queryResponses;

function getCrossChainData(
IWormholeERC7412Receiver.CrossChainRequest[] memory reqs,
uint256 maxAge
) external view override returns (bytes[] memory responses) {
responses = new bytes[](reqs.length);
for (uint i = 0; i < reqs.length; i++) {
CrossChainRequest memory req = reqs[i];
bytes32 reqHash = keccak256(abi.encodePacked(req.chainSelector, req.target, req.data));

responses[i] = queryResponses[reqHash];
}
}

function setCrossChainData(
uint64 chainSelector,
address target,
bytes memory callData,
bytes memory response
) external {
bytes32 reqHash = keccak256(abi.encodePacked(chainSelector, target, callData));
queryResponses[reqHash] = response;
}

function fulfillOracleQuery(bytes calldata signedOffchainData) external payable {}

function oracleId() external view returns (bytes32) {
return "DUMMY";
}
}
16 changes: 16 additions & 0 deletions protocol/synthetix/contracts/modules/core/CrossChainPoolModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,26 @@ contract CrossChainPoolModule is ICrossChainPoolModule {
event OCRResponse(bytes32 indexed requestId, bytes result, bytes err);

bytes32 internal constant _CREATE_CROSS_CHAIN_POOL_FEATURE_FLAG = "createCrossChainPool";
bytes32 internal constant _SET_CROSS_CHAIN_SELECTORS = "setCrossChainPoolSelectors";
bytes32 internal constant _SET_CROSS_CHAIN_POOL_CONFIGURATION_FEATURE_FLAG =
"setCrossChainPoolConfiguration";

string internal constant _CONFIG_CHAINLINK_FUNCTIONS_ADDRESS = "chainlinkFunctionsAddr";

function setCrossChainPoolSelectors(
uint128 poolId,
bytes4 readSelector,
bytes4 writeSelector
) external override {
FeatureFlag.ensureAccessToFeature(_SET_CROSS_CHAIN_SELECTORS);

Pool.Data storage pool = Pool.loadExisting(poolId);
Pool.onlyPoolOwner(poolId, msg.sender);

pool.crossChain[0].offchainReadSelector = readSelector;
pool.crossChain[0].broadcastSelector = writeSelector;
}

function createCrossChainPool(
uint128 sourcePoolId,
uint64 targetChainId
Expand Down Expand Up @@ -94,6 +109,7 @@ contract CrossChainPoolModule is ICrossChainPoolModule {

Pool.Data storage pool = Pool.loadExisting(poolId);
Pool.onlyPoolOwner(poolId, msg.sender);
Pool.onlyCrossChainConfigured(poolId);

if (newMarketConfigurations.length != pool.crossChain[0].pairedChains.length + 1) {
revert ParameterError.InvalidParameter(
Expand Down
123 changes: 78 additions & 45 deletions protocol/synthetix/contracts/modules/core/CrossChainUpkeepModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ contract CrossChainUpkeepModule is ICrossChainUpkeepModule {
PoolCrossChainSync.Data memory syncData,
int256 assignedDebt
) external override {
CrossChain.onlyCrossChain();
_receivePoolHeartbeat(poolId, syncData, assignedDebt);
}

Expand All @@ -57,9 +58,11 @@ contract CrossChainUpkeepModule is ICrossChainUpkeepModule {
pool.setCrossChainSyncData(syncData);

// assign accumulated debt
console.log("assign debt");
pool.assignDebt(assignedDebt);

// make sure the markets limits are set as expect
console.log("recalculate all collaterals");
pool.recalculateAllCollaterals();

emit PoolHeartbeat(poolId, syncData);
Expand Down Expand Up @@ -110,15 +113,82 @@ contract CrossChainUpkeepModule is ICrossChainUpkeepModule {
);
}

address(this).tryCall(
abi.encodeWithSelector(
pool.crossChain[0].offchainReadSelector,
pool.crossChain[0].subscriptionId,
pool.crossChain[0].pairedChains,
calls,
300000
)
bytes[] memory responses = abi.decode(
address(this).tryCall(
abi.encodeWithSelector(
pool.crossChain[0].offchainReadSelector,
pool.crossChain[0].subscriptionId,
pool.crossChain[0].pairedChains,
calls,
300000
)
),
(bytes[])
);

// now that we have cross chain data, lets parse the result
uint256 chainsCount = pool.crossChain[0].pairedChains.length;

uint256[] memory liquidityAmounts = new uint256[](chainsCount);

PoolCrossChainSync.Data memory sync;

sync.dataTimestamp = uint64(block.timestamp);

for (uint i = 0; i < chainsCount; i++) {
bytes[] memory chainResponses = abi.decode(responses[i], (bytes[]));

uint256 chainLiquidity = abi.decode(chainResponses[0], (uint256));
int256 chainCumulativeMarketDebt = abi.decode(chainResponses[1], (int256));
int256 chainTotalDebt = abi.decode(chainResponses[2], (int256));
uint256 chainSyncTime = abi.decode(chainResponses[3], (uint256));
uint256 chainLastPoolConfigTime = abi.decode(chainResponses[4], (uint256));

liquidityAmounts[i] = chainLiquidity;
sync.liquidity += chainLiquidity.to128();
sync.cumulativeMarketDebt += chainCumulativeMarketDebt.to128();
sync.totalDebt += chainTotalDebt.to128();

sync.oldestDataTimestamp = sync.oldestDataTimestamp < chainSyncTime
? sync.oldestDataTimestamp
: uint64(chainSyncTime);
sync.oldestPoolConfigTimestamp = sync.oldestPoolConfigTimestamp <
chainLastPoolConfigTime
? sync.oldestPoolConfigTimestamp
: uint64(chainLastPoolConfigTime);
}

int256 marketDebtChange = sync.cumulativeMarketDebt -
pool.crossChain[0].latestSync.cumulativeMarketDebt;
int256 remainingDebt = marketDebtChange;

// send sync message
for (uint i = 1; i < chainsCount; i++) {
uint64[] memory targetChainIds = new uint64[](1);
targetChainIds[0] = pool.crossChain[0].pairedChains[i];

int256 chainAssignedDebt = (marketDebtChange * liquidityAmounts[i].toInt()) /
sync.liquidity.toInt();
remainingDebt -= chainAssignedDebt;

// TODO: gas limit should be based on number of markets assigned to pool
// TODO: broadcast will be paid in LINK
address(this).tryCall(
abi.encodeWithSelector(
pool.crossChain[0].broadcastSelector,
pool.crossChain[0].pairedChains,
abi.encodeWithSelector(
this._recvPoolHeartbeat.selector,
pool.crossChain[0].pairedPoolIds[targetChainIds[0]],
sync,
chainAssignedDebt
),
500000
)
);
}

_receivePoolHeartbeat(pool.id, sync, remainingDebt);
}

function _encodeCrossChainPoolCall(uint128 poolId) internal pure returns (bytes memory) {
Expand Down Expand Up @@ -146,41 +216,4 @@ contract CrossChainUpkeepModule is ICrossChainUpkeepModule {

return abi.encodeWithSelector(IMulticallModule.multicall.selector, abi.encode(calls));
}

/**
* loads the bytes deployed to the specified address
* used to get the inline execution code for chainlink
*/
function _codeAt(address _addr) public view returns (bytes memory o_code) {
assembly {
// retrieve the size of the code, this needs assembly
let size := extcodesize(_addr)
// allocate output byte array - this could also be done without assembly
// by using o_code = new bytes(size)
o_code := mload(0x40)
// new "memory end" including padding
mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
// store length in memory
mstore(o_code, size)
// actually retrieve the code, this needs assembly
extcodecopy(_addr, add(o_code, 0x20), 0, size)
}
}

/**
* This is basically required to send binary data to chainlink functions
*/
function _bytesToHexString(bytes memory buffer) public pure returns (string memory) {
// Fixed buffer size for hexadecimal convertion
bytes memory converted = new bytes(buffer.length * 2);

bytes memory _base = "0123456789abcdef";

for (uint256 i = 0; i < buffer.length; i++) {
converted[i * 2] = _base[uint8(buffer[i]) / _base.length];
converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length];
}

return string(abi.encodePacked("0x", converted));
}
}
Loading

0 comments on commit 6613846

Please sign in to comment.