diff --git a/.gitmodules b/.gitmodules index 2b9f73b0..afd33bc4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,4 +7,4 @@ [submodule "lib/memview-sol"] path = lib/memview-sol url = https://github.com/summa-tx/memview-sol - branch = v2.1.1 + branch = main diff --git a/src/Connector.sol b/src/Connector.sol index dbf0fbce..deb21b31 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -5,12 +5,14 @@ pragma abicoder v2; import { RestrictedTokenFactoryLike, MemberlistFactoryLike } from "./token/factory.sol"; import { RestrictedTokenLike } from "./token/restricted.sol"; import { MemberlistLike } from "./token/memberlist.sol"; -import "forge-std/Test.sol"; +// TODO: remove dependency on Messages.sol +import { ConnectorMessages } from "src/Messages.sol"; interface RouterLike { + function sendMessage(uint64 poolId, bytes16 trancheId, uint256 amount, address user) external; } -contract CentrifugeConnector is Test { +contract CentrifugeConnector { RouterLike public router; RestrictedTokenFactoryLike public immutable tokenFactory; @@ -24,7 +26,7 @@ contract CentrifugeConnector is Test { struct Tranche { address token; - uint256 latestPrice; // [ray] + uint128 latestPrice; // [ray] uint256 lastPriceUpdate; // TODO: the token name & symbol need to be stored because of the separation between adding and deploying tranches. // This leads to duplicate storage (also in the ERC20 contract), ideally we should refactor this somehow @@ -86,7 +88,7 @@ contract CentrifugeConnector is Test { emit PoolAdded(poolId); } - function addTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) + function addTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, uint128 price) public onlyRouter { @@ -94,7 +96,8 @@ contract CentrifugeConnector is Test { require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); Tranche storage tranche = tranches[poolId][trancheId]; - tranche.latestPrice = 1*10**27; + tranche.latestPrice = price; + tranche.lastPriceUpdate = block.timestamp; tranche.tokenName = tokenName; tranche.tokenSymbol = tokenSymbol; @@ -102,26 +105,25 @@ contract CentrifugeConnector is Test { } function deployTranche(uint64 poolId, bytes16 trancheId) public { - Pool storage pool = pools[poolId]; - require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); - Tranche storage tranche = tranches[poolId][trancheId]; + require(tranche.lastPriceUpdate > 0, "CentrifugeConnector/invalid-pool-or-tranche"); + address token = tokenFactory.newRestrictedToken(tranche.tokenName, tranche.tokenSymbol); tranche.token = token; address memberlist = memberlistFactory.newMemberlist(); RestrictedTokenLike(token).depend("memberlist", memberlist); - + MemberlistLike(memberlist).updateMember(address(this), uint(-1)); // required to be able to receive tokens in case of withdrawals emit TrancheDeployed(poolId, trancheId, token); } function updateTokenPrice( uint64 poolId, bytes16 trancheId, - uint256 price + uint128 price ) public onlyRouter { Tranche storage tranche = tranches[poolId][trancheId]; - require(tranche.latestPrice > 0, "CentrifugeConnector/invalid-pool-or-tranche"); + require(tranche.lastPriceUpdate > 0, "CentrifugeConnector/invalid-pool-or-tranche"); tranche.latestPrice = price; tranche.lastPriceUpdate = block.timestamp; } @@ -130,16 +132,16 @@ contract CentrifugeConnector is Test { uint64 poolId, bytes16 trancheId, address user, - uint256 validUntil + uint64 validUntil ) public onlyRouter { Tranche storage tranche = tranches[poolId][trancheId]; - require(tranche.latestPrice > 0, "CentrifugeConnector/invalid-pool-or-tranche"); + require(tranche.lastPriceUpdate > 0, "CentrifugeConnector/invalid-pool-or-tranche"); RestrictedTokenLike token = RestrictedTokenLike(tranche.token); MemberlistLike memberlist = MemberlistLike(token.memberlist()); memberlist.updateMember(user, validUntil); } - function transferTo( + function handleTransfer( uint64 poolId, bytes16 trancheId, address user, @@ -147,7 +149,23 @@ contract CentrifugeConnector is Test { ) public onlyRouter { RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); require(token.hasMember(user), "CentrifugeConnector/not-a-member"); + token.mint(user, amount); } - + + function transfer( + uint64 poolId, + bytes16 trancheId, + address user, + uint256 amount, + ConnectorMessages.Domain domain + ) public { + require(domain == ConnectorMessages.Domain.Centrifuge, "CentrifugeConnector/invalid-domain"); + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + require(address(token) != address(0), "CentrifugeConnector/unknown-token"); + require(token.balanceOf(user) >= amount, "CentrifugeConnector/insufficient-balance"); + require(token.transferFrom(user, address(this), amount), "CentrifugeConnector/token-transfer-failed"); + token.burn(address(this), amount); + router.sendMessage(poolId, trancheId, amount, user); + } } diff --git a/src/Messages.sol b/src/Messages.sol index 14c9bae2..e3c3e4e7 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -13,9 +13,11 @@ library ConnectorMessages { AddTranche, UpdateTokenPrice, UpdateMember, - TransferTo + Transfer } + enum Domain { Centrifuge, EVM } + function messageType(bytes29 _msg) internal pure returns (Call _call) { _call = Call(uint8(_msg.indexUint(0, 1))); } @@ -43,26 +45,35 @@ library ConnectorMessages { * * 0: call type (uint8 = 1 byte) * 1-8: poolId (uint64 = 8 bytes) - * 9-25: trancheId (16 bytes) - * 26-154: tokenName (string = 128 bytes) + * 9-24: trancheId (16 bytes) + * 25-154: tokenName (string = 128 bytes) * 155-187: tokenSymbol (string = 32 bytes) + * 185-200: price (uint128 = 16 bytes) */ - function formatAddTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) internal pure returns (bytes memory) { + function formatAddTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, uint128 price) internal pure returns (bytes memory) { // TODO(nuno): Now, we encode `tokenName` as a 128-bytearray by first encoding `tokenName` // to bytes32 and then we encode three empty bytes32's, which sum up to a total of 128 bytes. - // Add support to actually encode `tokennName` fully as a 128 bytes string. - return abi.encodePacked(uint8(Call.AddTranche), poolId, trancheId, stringToBytes32(tokenName), bytes32(""), bytes32(""), bytes32(""), stringToBytes32(tokenSymbol)); + // Add support to actually encode `tokenName` fully as a 128 bytes string. + return abi.encodePacked( + uint8(Call.AddTranche), + poolId, + trancheId, + stringToBytes32(tokenName), bytes32(""), bytes32(""), bytes32(""), + stringToBytes32(tokenSymbol), + price + ); } function isAddTranche(bytes29 _msg) internal pure returns (bool) { return messageType(_msg) == Call.AddTranche; } - function parseAddTranche(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) { + function parseAddTranche(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, uint128 price) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); tokenName = bytes32ToString(bytes32(_msg.index(25, 32))); tokenSymbol = bytes32ToString(bytes32(_msg.index(153, 32))); + price = uint128(_msg.indexUint(185, 16)); } // TODO: should be moved to a util contract @@ -95,26 +106,27 @@ library ConnectorMessages { * * 0: call type (uint8 = 1 byte) * 1-8: poolId (uint64 = 8 bytes) - * 9-25: trancheId (16 bytes) - * 26-46: user (Ethereum address, 20 bytes) - * 47-78: validUntil (uint256 = 32 bytes) + * 9-24: trancheId (16 bytes) + * 25-45: user (Ethereum address, 20 bytes - Skip 12 bytes from 32-byte addresses) + * 57-65: validUntil (uint64 = 8 bytes) * * TODO: use bytes32 for user (for non-EVM compatibility) */ - function formatUpdateMember(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.UpdateMember), poolId, trancheId, user, validUntil); + function formatUpdateMember(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) internal pure returns (bytes memory) { + // NOTE: Since parseUpdateMember parses the first 20 bytes of `user` and skips the following 12 + // here we need to append 12 zeros to make it right. Drop once we support 32-byte addresses. + return abi.encodePacked(uint8(Call.UpdateMember), poolId, trancheId, user, bytes(hex"000000000000000000000000"), validUntil); } function isUpdateMember(bytes29 _msg) internal pure returns (bool) { return messageType(_msg) == Call.UpdateMember; } - function parseUpdateMember(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) { + function parseUpdateMember(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); user = address(bytes20(_msg.index(25, 20))); - // TODO: skip 12 padded zeroes from address - validUntil = uint256(_msg.index(45, 32)); + validUntil = uint64(_msg.indexUint(57, 8)); } /** @@ -122,10 +134,10 @@ library ConnectorMessages { * * 0: call type (uint8 = 1 byte) * 1-8: poolId (uint64 = 8 bytes) - * 9-25: trancheId (16 bytes) - * 26-58: price (uint256 = 32 bytes) + * 9-24: trancheId (16 bytes) + * 25-41: price (uint128 = 16 bytes) */ - function formatUpdateTokenPrice(uint64 poolId, bytes16 trancheId, uint256 price) internal pure returns (bytes memory) { + function formatUpdateTokenPrice(uint64 poolId, bytes16 trancheId, uint128 price) internal pure returns (bytes memory) { return abi.encodePacked(uint8(Call.UpdateTokenPrice), poolId, trancheId, price); } @@ -133,10 +145,44 @@ library ConnectorMessages { return messageType(_msg) == Call.UpdateTokenPrice; } - function parseUpdateTokenPrice(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, uint256 price) { + function parseUpdateTokenPrice(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, uint128 price) { + poolId = uint64(_msg.indexUint(1, 8)); + trancheId = bytes16(_msg.index(9, 16)); + price = uint128(_msg.indexUint(25, 16)); + } + + /** + * Transfer + * + * 0: call type (uint8 = 1 byte) + * 1-8: poolId (uint64 = 8 bytes) + * 9-24: trancheId (16 bytes) + * 25-56: user (Ethereum address, 20 bytes - Skip last 12 bytes for 32-byte address compatibility) + * 57-72: amount (uint128 = 16 bytes) + * 73-81: domain (Domain = 9 bytes) + */ + function formatTransfer(uint64 poolId, bytes16 trancheId, address user, uint128 amount, bytes9 destinationDomain) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.Transfer), poolId, trancheId, user, bytes(hex"000000000000000000000000"), amount, destinationDomain); + } + + function isTransfer(bytes29 _msg) internal pure returns (bool) { + return messageType(_msg) == Call.Transfer; + } + + function parseTransfer(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, address user, uint128 amount, bytes9 encodedDomain) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); - price = uint256(_msg.index(25, 32)); + user = address(bytes20(_msg.index(25, 20))); + amount = uint128(_msg.indexUint(57, 16)); + encodedDomain = bytes9(_msg.index(73, 9)); + } + + function formatDomain(Domain domain) public pure returns (bytes9) { + return bytes9(byte(uint8(domain))); + } + + function formatDomain(Domain domain, uint64 domainId) public pure returns (bytes9) { + return bytes9(abi.encodePacked(uint8(domain), domainId).ref(0).index(0, 9)); } } \ No newline at end of file diff --git a/src/routers/xcm/Router.sol b/src/routers/xcm/Router.sol index 3133dbd6..435a2c4f 100644 --- a/src/routers/xcm/Router.sol +++ b/src/routers/xcm/Router.sol @@ -7,9 +7,10 @@ import {ConnectorMessages} from "../../Messages.sol"; interface ConnectorLike { function addPool(uint64 poolId) external; - function addTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) external; - function updateMember(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) external; - function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint256 price) external; + function addTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, uint128 price) external; + function updateMember(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) external; + function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint128 price) external; + function handleTransfer(uint64 poolId, bytes16 trancheId, address user, uint256 amount) external; } contract ConnectorXCMRouter { @@ -26,30 +27,57 @@ contract ConnectorXCMRouter { connector = ConnectorLike(connector_); centrifugeChainOrigin = centrifugeChainOrigin_; } - + + modifier onlyCentrifugeChainOrigin() { require(msg.sender == address(centrifugeChainOrigin), "ConnectorXCMRouter/invalid-origin"); _; } + modifier onlyConnector() { + require(msg.sender == address(connector), "ConnectorXCMRouter/only-connector-allowed-to-call"); + _; + } + function handle( bytes memory _message ) external onlyCentrifugeChainOrigin { bytes29 _msg = _message.ref(0); - if (ConnectorMessages.isAddPool(_msg) == true) { + if (ConnectorMessages.isAddPool(_msg)) { uint64 poolId = ConnectorMessages.parseAddPool(_msg); connector.addPool(poolId); - } else if (ConnectorMessages.isAddTranche(_msg) == true) { - (uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) = ConnectorMessages.parseAddTranche(_msg); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol); - } else if (ConnectorMessages.isUpdateMember(_msg) == true) { - (uint64 poolId, bytes16 trancheId, address user, uint256 amount) = ConnectorMessages.parseUpdateMember(_msg); - connector.updateMember(poolId, trancheId, user, amount); - } else if (ConnectorMessages.isUpdateTokenPrice(_msg) == true) { - (uint64 poolId, bytes16 trancheId, uint256 price) = ConnectorMessages.parseUpdateTokenPrice(_msg); + } else if (ConnectorMessages.isAddTranche(_msg)) { + (uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, uint128 price) = ConnectorMessages.parseAddTranche(_msg); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + } else if (ConnectorMessages.isUpdateMember(_msg)) { + (uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) = ConnectorMessages.parseUpdateMember(_msg); + connector.updateMember(poolId, trancheId, user, validUntil); + } else if (ConnectorMessages.isUpdateTokenPrice(_msg)) { + (uint64 poolId, bytes16 trancheId, uint128 price) = ConnectorMessages.parseUpdateTokenPrice(_msg); connector.updateTokenPrice(poolId, trancheId, price); + } else if (ConnectorMessages.isTransfer(_msg)) { + (uint64 poolId, bytes16 trancheId, address user, uint256 amount, bytes9 _decodedDomain) = ConnectorMessages.parseTransfer(_msg); + connector.handleTransfer(poolId, trancheId, user, amount); } else { require(false, "invalid-message"); } } + + function sendMessage(uint64 poolId, bytes16 trancheId, uint256 amount, address user) external onlyConnector { + // TODO: implement + } + + function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) { + uint8 i = 0; + while (i < 32 && _bytes32[i] != 0) { + i++; + } + + bytes memory bytesArray = new bytes(i); + for (i = 0; i < 32 && _bytes32[i] != 0; i++) { + bytesArray[i] = _bytes32[i]; + } + return string(bytesArray); + } + } diff --git a/src/token/restricted.sol b/src/token/restricted.sol index 466e679d..15524dbe 100644 --- a/src/token/restricted.sol +++ b/src/token/restricted.sol @@ -12,6 +12,11 @@ interface ERC20Like { function mint(address usr, uint wad) external; function name() external view returns (string memory); function symbol() external view returns (string memory); + function balanceOf(address usr) external view returns (uint wad); + function burn(address usr, uint wad) external; + function transferFrom(address from, address to, uint amount) external returns (bool); + function totalSupply() external returns (uint); + function approve(address _spender, uint256 _value) external returns (bool); } interface RestrictedTokenLike is ERC20Like { diff --git a/test/Connector.t.sol b/test/Connector.t.sol index 8e01d0b3..aa41f574 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -11,12 +11,18 @@ import { ConnectorXCMRouter } from "src/routers/xcm/Router.sol"; import "forge-std/Test.sol"; import "../src/Connector.sol"; +interface ERC20Like { + function balanceOf(address) external view returns (uint); +} + contract ConnectorTest is Test { CentrifugeConnector bridgedConnector; ConnectorXCMRouter bridgedRouter; MockHomeConnector homeConnector; + uint minimumDelay; + function setUp() public { address tokenFactory_ = address(new RestrictedTokenFactory()); address memberlistFactory_ = address(new MemberlistFactory()); @@ -24,6 +30,7 @@ contract ConnectorTest is Test { bridgedConnector = new CentrifugeConnector(tokenFactory_, memberlistFactory_); homeConnector = new MockHomeConnector(address(bridgedConnector)); bridgedConnector.file("router", address(homeConnector.router())); + minimumDelay = new Memberlist().minimumDelay(); } function testAddingPoolWorks(uint64 poolId) public { @@ -37,21 +44,21 @@ contract ConnectorTest is Test { bridgedConnector.addPool(poolId); } - function testAddingSingleTrancheWorks(uint64 poolId, string memory tokenName, string memory tokenSymbol, bytes16 trancheId) public { + function testAddingSingleTrancheWorks(uint64 poolId, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 price) public { // 0. Add Pool homeConnector.addPool(poolId); (uint64 actualPoolId,) = bridgedConnector.pools(poolId); assertEq(uint256(actualPoolId), uint256(poolId)); // 1. Add the tranche - homeConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol); + homeConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); // 2. Then deploy the tranche bridgedConnector.deployTranche(poolId, trancheId); (address token_, uint256 latestPrice,,string memory actualTokenName, string memory actualTokenSymbol) = bridgedConnector.tranches(poolId, trancheId); assertTrue(token_ != address(0)); - assertTrue(latestPrice > 0); + assertEq(latestPrice, price); // Comparing raw input to output can erroneously fail when a byte string is given. // Intended behaviour is that byte strings will be treated as bytes and converted to strings @@ -65,36 +72,64 @@ contract ConnectorTest is Test { assertEq(token.symbol(), bytes32ToString(stringToBytes32(tokenSymbol))); } - function testAddingMultipleTranchesWorks(uint64 poolId, bytes16[] calldata trancheIds, string memory tokenName, string memory tokenSymbol) public { + function testAddingMultipleTranchesWorks(uint64 poolId, bytes16[] calldata trancheIds, string memory tokenName, string memory tokenSymbol, uint128 price) public { homeConnector.addPool(poolId); for (uint i = 0; i < trancheIds.length; i++) { - homeConnector.addTranche(poolId, trancheIds[i], tokenName, tokenSymbol); + uint128 tranchePrice = price + uint128(i); + homeConnector.addTranche(poolId, trancheIds[i], tokenName, tokenSymbol, tranchePrice); bridgedConnector.deployTranche(poolId, trancheIds[i]); (address token, uint256 latestPrice, , ,) = bridgedConnector.tranches(poolId, trancheIds[i]); - assertTrue(latestPrice > 0); + assertEq(latestPrice, tranchePrice); assertTrue(token != address(0)); } } - function testAddingTranchesAsNonRouterFails(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) public { + function testAddingTranchesAsNonRouterFails(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, uint128 price) public { homeConnector.addPool(poolId); vm.expectRevert(bytes("CentrifugeConnector/not-the-router")); - bridgedConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol); + bridgedConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); } - function testAddingTranchesForNonExistentPoolFails(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) public { + function testAddingTranchesForNonExistentPoolFails(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, uint128 price) public { vm.expectRevert(bytes("CentrifugeConnector/invalid-pool")); - homeConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol); + homeConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + } + + function testDeployingWrongTrancheFails(uint64 poolId, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, bytes16 wrongTrancheId, uint128 price) public { + vm.assume(trancheId != wrongTrancheId); + // 0. Add Pool + homeConnector.addPool(poolId); + (uint64 actualPoolId,) = bridgedConnector.pools(poolId); + assertEq(uint256(actualPoolId), uint256(poolId)); + + // 1. Add the tranche + homeConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + // 2. Then deploy the tranche + vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); + bridgedConnector.deployTranche(poolId, wrongTrancheId); + } + + function testDeployingTrancheOnNonExistentPoolFails(uint64 poolId, uint64 wrongPoolId, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 price) public { + vm.assume(poolId != wrongPoolId); + // 0. Add Pool + homeConnector.addPool(poolId); + (uint64 actualPoolId,) = bridgedConnector.pools(poolId); + assertEq(uint256(actualPoolId), uint256(poolId)); + + // 1. Add the tranche + homeConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + // 2. Then deploy the tranche + vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); + bridgedConnector.deployTranche(wrongPoolId, trancheId); } - function testUpdatingMemberWorks(uint64 poolId, bytes16 trancheId, address user, uint128 fuzzed_uint128) public { - vm.assume(fuzzed_uint128 > 0); - uint256 validUntil = safeAdd(fuzzed_uint128, safeAdd(block.timestamp, new Memberlist().minimumDelay())); + function testUpdatingMemberWorks(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) public { + vm.assume(validUntil >= safeAdd(block.timestamp, new Memberlist().minimumDelay())); vm.assume(user != address(0)); homeConnector.addPool(poolId); - homeConnector.addTranche(poolId, trancheId, "Some Name", "SYMBOL"); + homeConnector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", 123); bridgedConnector.deployTranche(poolId, trancheId); homeConnector.updateMember(poolId, trancheId, user, validUntil); @@ -106,45 +141,45 @@ contract ConnectorTest is Test { assertEq(memberlist.members(user), validUntil); } - function testUpdatingMemberBeforeMinimumDelayFails(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) public { + function testUpdatingMemberBeforeMinimumDelayFails(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) public { vm.assume(validUntil <= safeAdd(block.timestamp, new Memberlist().minimumDelay())); vm.assume(user != address(0)); homeConnector.addPool(poolId); - homeConnector.addTranche(poolId, trancheId, "Some Name", "SYMBOL"); + homeConnector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", 123); bridgedConnector.deployTranche(poolId, trancheId); vm.expectRevert("invalid-validUntil"); homeConnector.updateMember(poolId, trancheId, user, validUntil); } - function testUpdatingMemberAsNonRouterFails(uint64 poolId, bytes16 trancheId, address user, uint128 fuzzed_uint128) public { - vm.assume(fuzzed_uint128 > 0); - uint256 validUntil = safeAdd(fuzzed_uint128, safeAdd(block.timestamp, new Memberlist().minimumDelay())); + function testUpdatingMemberAsNonRouterFails(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) public { + vm.assume(validUntil <= safeAdd(block.timestamp, new Memberlist().minimumDelay())); vm.assume(user != address(0)); vm.expectRevert(bytes("CentrifugeConnector/not-the-router")); bridgedConnector.updateMember(poolId, trancheId, user, validUntil); } - function testUpdatingMemberForNonExistentPoolFails(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) public { + function testUpdatingMemberForNonExistentPoolFails(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) public { vm.assume(validUntil > block.timestamp); - vm.assume(user != address(0)); bridgedConnector.file("router", address(this)); vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); bridgedConnector.updateMember(poolId, trancheId, user, validUntil); } - function testUpdatingMemberForNonExistentTrancheFails(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) public { + + + function testUpdatingMemberForNonExistentTrancheFails(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) public { vm.assume(validUntil > block.timestamp); - vm.assume(user != address(0)); bridgedConnector.file("router", address(this)); bridgedConnector.addPool(poolId); vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); bridgedConnector.updateMember(poolId, trancheId, user, validUntil); } - function testUpdatingTokenPriceWorks(uint64 poolId, bytes16 trancheId, uint256 price) public { + + function testUpdatingTokenPriceWorks(uint64 poolId, bytes16 trancheId, uint128 price) public { homeConnector.addPool(poolId); - homeConnector.addTranche(poolId, trancheId, "Some Name", "SYMBOL"); + homeConnector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", 123); homeConnector.updateTokenPrice(poolId, trancheId, price); (, uint256 latestPrice, uint256 lastPriceUpdate, ,) = bridgedConnector.tranches(poolId, trancheId); @@ -152,30 +187,88 @@ contract ConnectorTest is Test { assertEq(lastPriceUpdate, block.timestamp); } - function testUpdatingTokenPriceAsNonRouterFails(uint64 poolId, bytes16 trancheId, uint256 price) public { + function testUpdatingTokenPriceAsNonRouterFails(uint64 poolId, bytes16 trancheId, uint128 price) public { homeConnector.addPool(poolId); - homeConnector.addTranche(poolId, trancheId, "Some Name", "SYMBOL"); + homeConnector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", 123); vm.expectRevert(bytes("CentrifugeConnector/not-the-router")); bridgedConnector.updateTokenPrice(poolId, trancheId, price); - } - function testUpdatingTokenPriceForNonExistentPoolFails(uint64 poolId, bytes16 trancheId, uint256 price) public { + + function testUpdatingTokenPriceForNonExistentPoolFails(uint64 poolId, bytes16 trancheId, uint128 price) public { bridgedConnector.file("router", address(this)); vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); bridgedConnector.updateTokenPrice(poolId, trancheId, price); } - function testUpdatingTokenPriceForNonExistentTrancheFails(uint64 poolId, bytes16 trancheId, uint256 price) public { + + function testUpdatingTokenPriceForNonExistentTrancheFails(uint64 poolId, bytes16 trancheId, uint128 price) public { bridgedConnector.file("router", address(this)); bridgedConnector.addPool(poolId); vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); bridgedConnector.updateTokenPrice(poolId, trancheId, price); } - function testTransferToWorks(uint64 poolId) public { } - function testTransferToAsNonRouterFails(uint64 poolId) public { } - function testTransferToForNonExistentPoolFails(uint64 poolId) public { } - function testTransferToForNonExistentTrancheFails(uint64 poolId) public { } + function testTransferCentrifuge(uint64 poolId, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 price, address user, uint128 amount, uint64 validUntil) public { + vm.assume(validUntil > block.timestamp + 7 days); + // 0. Add Pool + homeConnector.addPool(poolId); + + // 1. Add the tranche + homeConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + // 2. Then deploy the tranche + bridgedConnector.deployTranche(poolId, trancheId); + + // 3. Add member + homeConnector.updateMember(poolId, trancheId, user, validUntil); + + // 4. Transfer some tokens + bytes9 encodedDomain = ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge); + homeConnector.transfer(poolId, trancheId, user, amount, encodedDomain); + (address token,,,,) = bridgedConnector.tranches(poolId, trancheId); + assertEq(ERC20Like(token).balanceOf(user), amount); + } + + function testTransferEVM(uint64 poolId, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 price, uint64 destinationChainId, address user, uint128 amount, uint64 validUntil) public { + vm.assume(validUntil > block.timestamp + 7 days); + // 0. Add Pool + homeConnector.addPool(poolId); + + // 1. Add the tranche + homeConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + + // 2. Then deploy the tranche + bridgedConnector.deployTranche(poolId, trancheId); + + // 3. Add member + homeConnector.updateMember(poolId, trancheId, user, validUntil); + + // 4. Transfer some tokens + bytes9 encodedDomain = ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, destinationChainId); + homeConnector.transfer(poolId, trancheId, user, amount, encodedDomain); + (address token,,,,) = bridgedConnector.tranches(poolId, trancheId); + assertEq(ERC20Like(token).balanceOf(user), amount); + } + + function testTransferEVMWithoutMemberFails(uint64 poolId, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 price, uint64 destinationChainId, address user, uint128 amount, uint64 validUntil) public { + vm.assume(validUntil > block.timestamp + 7 days); + // 0. Add Pool + homeConnector.addPool(poolId); + + // 1. Add the tranche + homeConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + + // 2. Then deploy the tranche + bridgedConnector.deployTranche(poolId, trancheId); + + // 3. Transfer some tokens and expect revert + bytes9 encodedDomain = ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, destinationChainId); + vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + homeConnector.transfer(poolId, trancheId, user, amount, encodedDomain); + (address token,,,,) = bridgedConnector.tranches(poolId, trancheId); + assertEq(ERC20Like(token).balanceOf(user), 0); + } + + // helpers function safeAdd(uint x, uint y) internal pure returns (uint z) { require((z = x + y) >= x, "math-add-overflow"); } @@ -204,4 +297,20 @@ contract ConnectorTest is Test { return string(bytesArray); } -} \ No newline at end of file + function toBytes32(bytes memory f) internal pure returns (bytes16 fc) { + assembly { + fc := mload(add(f, 32)) + } + return fc; + } + + function toBytes29(bytes memory f) internal pure returns (bytes29 fc) { + assembly { + fc := mload(add(f, 29)) + } + return fc; + } + +} + + diff --git a/test/Messages.t.sol b/test/Messages.t.sol index d1b05fdf..2674af97 100644 --- a/test/Messages.t.sol +++ b/test/Messages.t.sol @@ -56,96 +56,105 @@ contract MessagesTest is Test { function testAddTrancheEncoding() public { assertEq( - ConnectorMessages.formatAddTranche(0, toBytes16(fromHex("010000000000000064")), "Some Name", "SYMBOL"), - fromHex("02000000000000000000000000000000000000000000000009536f6d65204e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053594d424f4c0000000000000000000000000000000000000000000000000000") + ConnectorMessages.formatAddTranche(12378532, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), "Some Name", "SYMBOL", 1000000000000000000000000000), + hex"020000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b536f6d65204e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053594d424f4c000000000000000000000000000000000000000000000000000000000000033b2e3c9fd0803ce8000000" ); } function testAddTrancheDecoding() public { - (uint64 decodedPoolId, bytes16 decodedTrancheId, string memory decodedTokenName, string memory decodedTokenSymbol) = ConnectorMessages.parseAddTranche(fromHex("02000000000000000000000000000000000000000000000009536f6d65204e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053594d424f4c0000000000000000000000000000000000000000000000000000").ref(0)); - assertEq(uint(decodedPoolId), uint(0)); - assertEq(decodedTrancheId, toBytes16(fromHex("010000000000000064"))); - assertEq(decodedTokenName, "Some Name"); - assertEq(decodedTokenSymbol, "SYMBOL"); + (uint64 decodedPoolId, bytes16 decodedTrancheId, string memory decodedTokenName, string memory decodedTokenSymbol, uint128 decodedPrice) = ConnectorMessages.parseAddTranche(fromHex("020000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b536f6d65204e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053594d424f4c000000000000000000000000000000000000000000000000000000000000033b2e3c9fd0803ce8000000").ref(0)); + assertEq(uint(decodedPoolId), uint(12378532)); + assertEq(decodedTrancheId, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b")); + assertEq(decodedTokenName, bytes32ToString(bytes32("Some Name"))); + assertEq(decodedTokenSymbol, bytes32ToString(bytes32("SYMBOL"))); + assertEq(decodedPrice, uint(1000000000000000000000000000)); } - function testAddTrancheEquivalence(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) + function testAddTrancheEquivalence(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, uint128 price) public { bytes memory _message = ConnectorMessages.formatAddTranche( poolId, trancheId, tokenName, - tokenSymbol + tokenSymbol, + price ); - (uint64 decodedPoolId, bytes16 decodedTrancheId, string memory decodedTokenName, string memory decodedTokenSymbol) = ConnectorMessages + (uint64 decodedPoolId, bytes16 decodedTrancheId, string memory decodedTokenName, string memory decodedTokenSymbol, uint128 decodedPrice) = ConnectorMessages .parseAddTranche(_message.ref(0)); - assertEq(uint256(decodedPoolId), uint256(poolId)); + assertEq(uint(decodedPoolId), uint(poolId)); assertEq(decodedTrancheId, trancheId); - // Comparing raw input to output can erroneously fail when a byte string is given. - // Intended behaviour is that byte strings will be treated as bytes and converted to strings instead of treated as strings themselves. - // This conversion from string to bytes32 to string is used to simulate this intended behaviour. + // Comparing raw input to output can erroneously fail when a byte string is given. + // Intended behaviour is that byte strings will be treated as bytes and converted to strings instead + // of treated as strings themselves. This conversion from string to bytes32 to string is used to simulate + // this intended behaviour. assertEq(decodedTokenName, bytes32ToString(stringToBytes32(tokenName))); assertEq(decodedTokenSymbol, bytes32ToString(stringToBytes32(tokenSymbol))); + assertEq(uint(decodedPrice), uint(price)); } + // Note: UpdateMember encodes differently in Solidity compared to the Rust counterpart because `user` is a 20-byte + // value in Solidity while it is 32-byte in Rust. However, UpdateMember messages coming from the cent-chain will + // be handled correctly as the last 12 bytes out of said 32 will be ignored. function testUpdateMemberEncoding() public { assertEq( - ConnectorMessages.formatUpdateMember(5, toBytes16(fromHex("010000000000000003")), 0x225ef95fa90f4F7938A5b34234d14768cB4263dd, 1657870537), - fromHex("04000000000000000500000000000000000000000000000009225ef95fa90f4f7938a5b34234d14768cb4263dd0000000000000000000000000000000000000000000000000000000062d118c9") - ); + ConnectorMessages.formatUpdateMember(2, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), 0x1231231231231231231231231231231231231231, 1706260138), + hex"040000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000065b376aa" + ); } + // We use an UpdateMember encoded message generated in the cent-chain to + // verify we handle the 32 to 20 bytes address compatibility as expected. function testUpdateMemberDecoding() public { - (uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedUser, uint256 decodedValidUntil) = ConnectorMessages.parseUpdateMember(fromHex("04000000000000000500000000000000000000000000000009225ef95fa90f4f7938a5b34234d14768cb4263dd0000000000000000000000000000000000000000000000000000000062d118c9").ref(0)); - assertEq(uint(decodedPoolId), uint(5)); - assertEq(decodedTrancheId, toBytes16(fromHex("010000000000000003"))); - assertEq(decodedUser, 0x225ef95fa90f4F7938A5b34234d14768cB4263dd); - assertEq(decodedValidUntil, uint(1657870537)); + (uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedUser, uint64 decodedValidUntil) = ConnectorMessages.parseUpdateMember(fromHex("040000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000065b376aa").ref(0)); + assertEq(uint(decodedPoolId), uint(2)); + assertEq(decodedTrancheId, hex"811acd5b3f17c06841c7e41e9e04cb1b"); + assertEq(decodedUser, 0x1231231231231231231231231231231231231231); + assertEq(decodedValidUntil, uint(1706260138)); } function testUpdateMemberEquivalence( uint64 poolId, bytes16 trancheId, address user, - uint256 amount + uint64 validUntil ) public { bytes memory _message = ConnectorMessages.formatUpdateMember( poolId, trancheId, user, - amount + validUntil ); ( uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedUser, - uint256 decodedAmount + uint64 decodedValidUntil ) = ConnectorMessages.parseUpdateMember(_message.ref(0)); - assertEq(uint256(decodedPoolId), uint256(poolId)); + assertEq(uint(decodedPoolId), uint(poolId)); assertEq(decodedTrancheId, trancheId); assertEq(decodedUser, user); - assertEq(decodedAmount, amount); + assertEq(uint(decodedValidUntil), uint(validUntil)); } function testUpdateTokenPriceEncoding() public { assertEq( - ConnectorMessages.formatUpdateTokenPrice(3, toBytes16(fromHex("010000000000000005")), 100), - fromHex("030000000000000003000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000064") + ConnectorMessages.formatUpdateTokenPrice(1, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), 1000000000000000000000000000), + fromHex("030000000000000001811acd5b3f17c06841c7e41e9e04cb1b00000000033b2e3c9fd0803ce8000000") ); } function testUpdateTokenPriceDecoding() public { - (uint64 decodedPoolId, bytes16 decodedTrancheId, uint256 decodedPrice) = ConnectorMessages.parseUpdateTokenPrice(fromHex("030000000000000003000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000064").ref(0)); - assertEq(uint(decodedPoolId), uint(3)); - assertEq(decodedTrancheId, toBytes16(fromHex("010000000000000005"))); - assertEq(decodedPrice, uint(100)); + (uint64 decodedPoolId, bytes16 decodedTrancheId, uint128 decodedPrice) = ConnectorMessages.parseUpdateTokenPrice(fromHex("030000000000000001811acd5b3f17c06841c7e41e9e04cb1b00000000033b2e3c9fd0803ce8000000").ref(0)); + assertEq(uint(decodedPoolId), uint(1)); + assertEq(decodedTrancheId, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b")); + assertEq(decodedPrice, uint(1000000000000000000000000000)); } function testUpdateTokenPriceEquivalence( uint64 poolId, bytes16 trancheId, - uint256 price + uint128 price ) public { bytes memory _message = ConnectorMessages.formatUpdateTokenPrice( poolId, @@ -155,11 +164,70 @@ contract MessagesTest is Test { ( uint64 decodedPoolId, bytes16 decodedTrancheId, - uint256 decodedPrice + uint128 decodedPrice ) = ConnectorMessages.parseUpdateTokenPrice(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); - assertEq(decodedPrice, price); + assertEq(uint(decodedPrice), uint(price)); + } + + function testTransferToEvmDomainEncoding() public { + assertEq( + ConnectorMessages.formatTransfer(1, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), 0x1231231231231231231231231231231231231231, 1000000000000000000000000000, ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, 1284)), + hex"050000000000000001811acd5b3f17c06841c7e41e9e04cb1b123123123123123123123123123123123123123100000000000000000000000000000000033b2e3c9fd0803ce8000000010000000000000504" + ); + } + + function testTransferToEvmDomainDecoding() public { + (uint64 poolId, bytes16 trancheId, address user, uint256 amount, bytes9 decodedDomain) = ConnectorMessages.parseTransfer(fromHex("050000000000000001811acd5b3f17c06841c7e41e9e04cb1b123123123123123123123123123123123123123100000000000000000000000000000000033b2e3c9fd0803ce8000000010000000000000504").ref(0)); + assertEq(uint(poolId), uint(1)); + assertEq(trancheId, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b")); + assertEq(user, 0x1231231231231231231231231231231231231231); + assertEq(amount, uint(1000000000000000000000000000)); + assertEq(decodedDomain, bytes9(hex"010000000000000504")); + } + + function testTransferToCentrifugeEncoding() public { + assertEq( + ConnectorMessages.formatTransfer(1, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), 0x1231231231231231231231231231231231231231, 1000000000000000000000000000, ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge)), + hex"050000000000000001811acd5b3f17c06841c7e41e9e04cb1b123123123123123123123123123123123123123100000000000000000000000000000000033b2e3c9fd0803ce8000000000000000000000000" + ); + } + + function testTransferToCentrifugeDecoding() public { + (uint64 poolId, bytes16 trancheId, address user, uint256 amount, bytes9 decodedDomain) = ConnectorMessages.parseTransfer(fromHex("050000000000000001811acd5b3f17c06841c7e41e9e04cb1b123123123123123123123123123123123123123100000000000000000000000000000000033b2e3c9fd0803ce8000000000000000000000000").ref(0)); + assertEq(uint(poolId), uint(1)); + assertEq(trancheId, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b")); + assertEq(user, 0x1231231231231231231231231231231231231231); + assertEq(amount, uint(1000000000000000000000000000)); + assertEq(decodedDomain, bytes9(hex"000000000000000000")); + } + + function testTransferEquivalence(uint64 poolId, bytes16 trancheId, address user, uint128 amount, uint64 destinationChainId) public { + bytes9 inputEncodedDomain = ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, destinationChainId); + bytes memory _message = ConnectorMessages.formatTransfer(poolId, trancheId, user, amount, inputEncodedDomain); + (uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedUser, uint256 decodedAmount, bytes9 encodedDomain) = ConnectorMessages.parseTransfer(_message.ref(0)); + assertEq(uint(decodedPoolId), uint(poolId)); + assertEq(decodedTrancheId, trancheId); + assertEq(decodedUser, user); + assertEq(decodedAmount, amount); + assertEq(encodedDomain, inputEncodedDomain); + } + + function testFormatDomainCentrifuge() public { + assertEq(ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge), hex"000000000000000000"); + } + + function testFormatDomainMoonbeam() public { + assertEq(ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, 1284), hex"010000000000000504"); + } + + function testFormatDomainMoonbaseAlpha() public { + assertEq(ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, 1287), hex"010000000000000507"); + } + + function testFormatDomainAvalanche() public { + assertEq(ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, 43114), hex"01000000000000a86a"); } // Convert an hexadecimal character to their value @@ -192,13 +260,6 @@ contract MessagesTest is Test { return r; } - function toBytes16(bytes memory f) internal pure returns (bytes16 fc) { - assembly { - fc := mload(add(f, 16)) - } - return fc; - } - function stringToBytes32(string memory source) internal pure returns (bytes32 result) { bytes memory tempEmptyStringTest = bytes(source); if (tempEmptyStringTest.length == 0) { diff --git a/test/mock/MockHomeConnector.sol b/test/mock/MockHomeConnector.sol index 149cc27a..3cc74292 100644 --- a/test/mock/MockHomeConnector.sol +++ b/test/mock/MockHomeConnector.sol @@ -15,9 +15,15 @@ contract MockHomeConnector is Test { ConnectorXCMRouter public immutable router; uint32 immutable CENTRIFUGE_CHAIN_DOMAIN = 3000; - uint32 immutable NONCE = 1; + + uint32 public dispatchDomain; + bytes public dispatchMessage; + bytes32 public dispatchRecipient; + uint public dispatchCalls; + + enum Types { AddPool } @@ -31,20 +37,37 @@ contract MockHomeConnector is Test { router.handle(_message); } - function addTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) public { - bytes memory _message = ConnectorMessages.formatAddTranche(poolId, trancheId, tokenName, tokenSymbol); + function addTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, uint128 price) public { + bytes memory _message = ConnectorMessages.formatAddTranche(poolId, trancheId, tokenName, tokenSymbol, price); router.handle(_message); } - function updateMember(uint64 poolId, bytes16 trancheId, address user, uint256 amount) public { - bytes memory _message = ConnectorMessages.formatUpdateMember(poolId, trancheId, user, amount); + function updateMember(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) public { + bytes memory _message = ConnectorMessages.formatUpdateMember(poolId, trancheId, user, validUntil); router.handle(_message); } - function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint256 price) public { + function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint128 price) public { bytes memory _message = ConnectorMessages.formatUpdateTokenPrice(poolId, trancheId, price); router.handle(_message); } + function transfer(uint64 poolId, bytes16 trancheId, address user, uint128 amount, bytes9 destinationDomain) public { + bytes memory _message = ConnectorMessages.formatTransfer(poolId, trancheId, user, amount, destinationDomain); + router.handle(_message); + } + + + function dispatch( + uint32 _destinationDomain, + bytes32 _recipientAddress, + bytes memory _messageBody + ) external { + dispatchCalls++; + dispatchDomain = _destinationDomain; + dispatchMessage = _messageBody; + dispatchRecipient = _recipientAddress; + } + }