diff --git a/src/Connector.sol b/src/Connector.sol index 68b353cb..2c62f40a 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -10,16 +10,29 @@ interface GatewayLike { function transferTrancheTokensToCentrifuge( uint64 poolId, bytes16 trancheId, + address sender, bytes32 destinationAddress, uint128 amount ) external; function transferTrancheTokensToEVM( uint64 poolId, bytes16 trancheId, - uint256 destinationChainId, + address sender, + uint64 destinationChainId, address destinationAddress, uint128 amount ) external; + function transfer(uint128 currency, address sender, bytes32 recipient, uint128 amount) external; + function increaseInvestOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + external; + function decreaseInvestOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + external; + function increaseRedeemOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + external; + function decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + external; + function collectInvest(uint64 poolId, bytes16 trancheId, address investor) external; + function collectRedeem(uint64 poolId, bytes16 trancheId, address investor) external; } interface EscrowLike { @@ -29,7 +42,6 @@ interface EscrowLike { struct Pool { uint64 poolId; uint256 createdAt; - address currency; } struct Tranche { @@ -40,6 +52,7 @@ struct Tranche { // This leads to duplicate storage (also in the ERC20 contract), ideally we should refactor this somehow string tokenName; string tokenSymbol; + uint8 decimals; } contract CentrifugeConnector { @@ -47,8 +60,14 @@ contract CentrifugeConnector { mapping(uint64 => Pool) public pools; mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; + mapping(uint128 => address) public currencyIdToAddress; + // The reverse mapping of `currencyIdToAddress` + mapping(address => uint128) public currencyAddressToId; + + mapping(uint64 => mapping(address => bool)) public allowedPoolCurrencies; + GatewayLike public gateway; - EscrowLike public escrow; + EscrowLike public immutable escrow; TrancheTokenFactoryLike public immutable tokenFactory; MemberlistFactoryLike public immutable memberlistFactory; @@ -57,7 +76,9 @@ contract CentrifugeConnector { event Rely(address indexed user); event Deny(address indexed user); event File(bytes32 indexed what, address data); + event CurrencyAdded(uint128 indexed currency, address indexed currencyAddress); event PoolAdded(uint256 indexed poolId); + event PoolCurrencyAllowed(uint128 currency, uint64 poolId); event TrancheAdded(uint256 indexed poolId, bytes16 indexed trancheId); event TrancheDeployed(uint256 indexed poolId, bytes16 indexed trancheId, address indexed token); @@ -98,6 +119,17 @@ contract CentrifugeConnector { } // --- Outgoing message handling --- + function transfer(address currencyAddress, bytes32 recipient, uint128 amount) public { + uint128 currency = currencyAddressToId[currencyAddress]; + require(currency != 0, "CentrifugeConnector/unknown-currency"); + + ERC20Like erc20 = ERC20Like(currencyAddress); + require(erc20.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); + require(erc20.transferFrom(msg.sender, address(escrow), amount), "CentrifugeConnector/currency-transfer-failed"); + + gateway.transfer(currency, msg.sender, recipient, amount); + } + function transferTrancheTokensToCentrifuge( uint64 poolId, bytes16 trancheId, @@ -110,13 +142,13 @@ contract CentrifugeConnector { require(token.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); token.burn(msg.sender, amount); - gateway.transferTrancheTokensToCentrifuge(poolId, trancheId, destinationAddress, amount); + gateway.transferTrancheTokensToCentrifuge(poolId, trancheId, msg.sender, destinationAddress, amount); } function transferTrancheTokensToEVM( uint64 poolId, bytes16 trancheId, - uint256 destinationChainId, + uint64 destinationChainId, address destinationAddress, uint128 amount ) public { @@ -126,63 +158,91 @@ contract CentrifugeConnector { require(token.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); token.burn(msg.sender, amount); - gateway.transferTrancheTokensToEVM(poolId, trancheId, destinationChainId, destinationAddress, amount); + gateway.transferTrancheTokensToEVM( + poolId, trancheId, msg.sender, destinationChainId, destinationAddress, amount + ); } - function increaseInvestOrder(uint64 poolId, bytes16 trancheId, uint128 amount) public { - Pool storage pool = pools[poolId]; - require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); - + function increaseInvestOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); - require(address(token) != address(0), "CentrifugeConnector/unknown-token"); + require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + uint128 currency = currencyAddressToId[currencyAddress]; + require(currency != 0, "CentrifugeConnector/unknown-currency"); + require(allowedPoolCurrencies[poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); + require( - ERC20Like(pool.currency).transferFrom(msg.sender, address(escrow), amount), + ERC20Like(currencyAddress).transferFrom(msg.sender, address(escrow), amount), "Centrifuge/Connector/currency-transfer-failed" ); - // TODO: send message to the gateway. Depends on https://github.com/centrifuge/connectors/pull/52 + gateway.increaseInvestOrder(poolId, trancheId, msg.sender, currency, amount); } - function decreaseInvestOrder(uint64 poolId, bytes16 trancheId, uint128 amount) public { - Pool storage pool = pools[poolId]; - require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); - + function decreaseInvestOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); - require(address(token) != address(0), "CentrifugeConnector/unknown-token"); + require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); - // TODO: send message to the gateway. Depends on https://github.com/centrifuge/connectors/pull/52 - } + uint128 currency = currencyAddressToId[currencyAddress]; + require(currency != 0, "CentrifugeConnector/unknown-currency"); + require(allowedPoolCurrencies[poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); - function increaseRedeemOrder(uint64 poolId, bytes16 trancheId, uint128 amount) public { - // TODO(nuno) + gateway.decreaseInvestOrder(poolId, trancheId, msg.sender, currency, amount); } - function decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, uint128 amount) public { - // TODO(nuno) - } + function increaseRedeemOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); + require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); - function collectRedeem(uint64 poolId, bytes16 trancheId) public { - // TODO(nuno) + uint128 currency = currencyAddressToId[currencyAddress]; + require(currency != 0, "CentrifugeConnector/unknown-currency"); + require(allowedPoolCurrencies[poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); + + gateway.increaseRedeemOrder(poolId, trancheId, msg.sender, currency, amount); } - function collectForRedeem(uint64 poolId, bytes16 trancheId, bytes32 userAddress) public { - // TODO(nuno) + function decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); + require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + + uint128 currency = currencyAddressToId[currencyAddress]; + require(currency != 0, "CentrifugeConnector/unknown-currency"); + require(allowedPoolCurrencies[poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); + + gateway.decreaseRedeemOrder(poolId, trancheId, msg.sender, currency, amount); } function collectInvest(uint64 poolId, bytes16 trancheId) public { - // TODO(nuno) + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); + require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + + gateway.collectInvest(poolId, trancheId, address(msg.sender)); } - function collectForInvest(uint64 poolId, bytes16 trancheId, bytes32 userAddress) public { - // TODO(nuno) + function collectRedeem(uint64 poolId, bytes16 trancheId) public { + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); + require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + + gateway.collectRedeem(poolId, trancheId, address(msg.sender)); } // --- Incoming message handling --- - // todo(nuno): store currency and decimals - function addPool(uint64 poolId, uint128 currency, uint8 decimals) public onlyGateway { + function addCurrency(uint128 currency, address currencyAddress) public onlyGateway { + require(currencyIdToAddress[currency] == address(0), "CentrifugeConnector/currency-id-in-use"); + require(currencyAddressToId[currencyAddress] == 0, "CentrifugeConnector/currency-address-in-use"); + + currencyIdToAddress[currency] = currencyAddress; + currencyAddressToId[currencyAddress] = currency; + emit CurrencyAdded(currency, currencyAddress); + } + + function addPool(uint64 poolId) public onlyGateway { Pool storage pool = pools[poolId]; require(pool.createdAt == 0, "CentrifugeConnector/pool-already-added"); pool.poolId = poolId; @@ -190,11 +250,23 @@ contract CentrifugeConnector { emit PoolAdded(poolId); } + function allowPoolCurrency(uint64 poolId, uint128 currency) public onlyGateway { + Pool storage pool = pools[poolId]; + require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); + + address currencyAddress = currencyIdToAddress[currency]; + require(currencyAddress != address(0), "CentrifugeConnector/unknown-currency"); + + allowedPoolCurrencies[poolId][currencyAddress] = true; + emit PoolCurrencyAllowed(currency, poolId); + } + function addTranche( uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price ) public onlyGateway { Pool storage pool = pools[poolId]; @@ -206,6 +278,7 @@ contract CentrifugeConnector { tranche.lastPriceUpdate = block.timestamp; tranche.tokenName = tokenName; tranche.tokenSymbol = tokenSymbol; + tranche.decimals = decimals; emit TrancheAdded(poolId, trancheId); } @@ -215,10 +288,8 @@ contract CentrifugeConnector { require(tranche.lastPriceUpdate > 0, "CentrifugeConnector/invalid-pool-or-tranche"); require(tranche.token == address(0), "CentrifugeConnector/tranche-already-deployed"); - // TODO: use actual decimals - uint8 decimals = 18; address token = - tokenFactory.newTrancheToken(poolId, trancheId, tranche.tokenName, tranche.tokenSymbol, decimals); + tokenFactory.newTrancheToken(poolId, trancheId, tranche.tokenName, tranche.tokenSymbol, tranche.decimals); tranche.token = token; address memberlist = memberlistFactory.newMemberlist(); @@ -242,14 +313,21 @@ contract CentrifugeConnector { memberlist.updateMember(user, validUntil); } - function handleTransferTrancheTokens( - uint64 poolId, - bytes16 trancheId, - uint256 destinationChainId, - address destinationAddress, - uint128 amount - ) public onlyGateway { - require(destinationChainId == block.chainid, "CentrifugeConnector/invalid-chain-id"); + function handleTransfer(uint128 currency, address recipient, uint128 amount) public onlyGateway { + address currencyAddress = currencyIdToAddress[currency]; + require(currencyAddress != address(0), "CentrifugeConnector/unknown-currency"); + + EscrowLike(escrow).approve(currencyAddress, address(this), amount); + require( + ERC20Like(currencyAddress).transferFrom(address(escrow), recipient, amount), + "CentrifugeConnector/currency-transfer-failed" + ); + } + + function handleTransferTrancheTokens(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) + public + onlyGateway + { RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); require(address(token) != address(0), "CentrifugeConnector/unknown-token"); diff --git a/src/Messages.sol b/src/Messages.sol index 33192b4c..c33c0061 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -11,34 +11,34 @@ library ConnectorMessages { /// 0 - An invalid message { Invalid, - /// 1 - Add Pool + /// 1 - Add a currency id -> EVM address mapping + AddCurrency, + /// 2 - Add Pool AddPool, - /// 2 - Add a Pool's Tranche Token + /// 3 - Allow a registered currency to be used as a pool currency or as an investment currency + AllowPoolCurrency, + /// 4 - Add a Pool's Tranche Token AddTranche, - /// 3 - Update the price of a Tranche Token + /// 5 - Update the price of a Tranche Token UpdateTrancheTokenPrice, - /// 4 - Update the member list of a tranche token with a new member + /// 6 - Update the member list of a tranche token with a new member UpdateMember, - /// 5 - A transfer of Stable Coins + /// 7 - A transfer of Stable Coins Transfer, - /// 6 - A transfer of Tranche tokens + /// 8 - A transfer of Tranche tokens TransferTrancheTokens, - /// 7 - Increase an investment order by a given amount + /// 9 - Increase an investment order by a given amount IncreaseInvestOrder, - /// 8 - Decrease an investment order by a given amount + /// 10 - Decrease an investment order by a given amount DecreaseInvestOrder, - /// 9 - Increase a Redeem order by a given amount + /// 11 - Increase a Redeem order by a given amount IncreaseRedeemOrder, - /// 10 - Decrease a Redeem order by a given amount + /// 12 - Decrease a Redeem order by a given amount DecreaseRedeemOrder, - /// 11 - Collect Redeem - CollectRedeem, - /// 12 - Collect for another user - CollectForRedeem, /// 13 - Collect investment CollectInvest, - /// 14 - Collect investment for another user - CollectForInvest + /// 14 - Collect Redeem + CollectRedeem } enum Domain { @@ -50,26 +50,62 @@ library ConnectorMessages { _call = Call(uint8(_msg.indexUint(0, 1))); } + /** + * Add Currency + * + * 0: call type (uint8 = 1 byte) + * 1-16: The Connector's global currency id (uint128 = 16 bytes) + * 17-36: The EVM address of the currency (address = 20 bytes) + */ + function formatAddCurrency(uint128 currency, address currencyAddress) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.AddCurrency), currency, currencyAddress); + } + + function isAddCurrency(bytes29 _msg) internal pure returns (bool) { + return messageType(_msg) == Call.AddCurrency; + } + + function parseAddCurrency(bytes29 _msg) internal pure returns (uint128 currency, address currencyAddress) { + currency = uint128(_msg.indexUint(1, 16)); + currencyAddress = address(bytes20(_msg.index(17, 20))); + } + /** * Add pool * * 0: call type (uint8 = 1 byte) * 1-8: poolId (uint64 = 8 bytes) - * 9-24: The pool currency id (uint128 = 16 bytes) - * 25: Currency decimals (uint8 = 1 byte) */ - function formatAddPool(uint64 poolId, uint128 currency, uint8 decimals) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.AddPool), poolId, currency, decimals); + function formatAddPool(uint64 poolId) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.AddPool), poolId); } function isAddPool(bytes29 _msg) internal pure returns (bool) { return messageType(_msg) == Call.AddPool; } - function parseAddPool(bytes29 _msg) internal pure returns (uint64 poolId, uint128 currency, uint8 decimals) { + function parseAddPool(bytes29 _msg) internal pure returns (uint64 poolId) { + poolId = uint64(_msg.indexUint(1, 8)); + } + + /** + * Allow Pool Currency + * + * 0: call type (uint8 = 1 byte) + * 1-8: poolId (uint64 = 8 bytes) + * 9-24: currency (uint128 = 16 bytes) + */ + function formatAllowPoolCurrency(uint64 poolId, uint128 currency) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.AllowPoolCurrency), poolId, currency); + } + + function isAllowPoolCurrency(bytes29 _msg) internal pure returns (bool) { + return messageType(_msg) == Call.AllowPoolCurrency; + } + + function parseAllowPoolCurrency(bytes29 _msg) internal pure returns (uint64 poolId, uint128 currency) { poolId = uint64(_msg.indexUint(1, 8)); currency = uint128(_msg.indexUint(9, 16)); - decimals = uint8(_msg.indexUint(25, 1)); } /** @@ -78,15 +114,17 @@ library ConnectorMessages { * 0: call type (uint8 = 1 byte) * 1-8: poolId (uint64 = 8 bytes) * 9-24: trancheId (16 bytes) - * 25-154: tokenName (string = 128 bytes) - * 155-187: tokenSymbol (string = 32 bytes) - * 185-200: price (uint128 = 16 bytes) + * 25-152: tokenName (string = 128 bytes) + * 153-184: tokenSymbol (string = 32 bytes) + * 185: decimals (uint8 = 1 byte) + * 186-202: price (uint128 = 16 bytes) */ function formatAddTranche( uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price ) internal pure returns (bytes memory) { // TODO(nuno): Now, we encode `tokenName` as a 128-bytearray by first encoding `tokenName` @@ -101,6 +139,7 @@ library ConnectorMessages { bytes32(""), bytes32(""), stringToBytes32(tokenSymbol), + decimals, price ); } @@ -112,13 +151,21 @@ library ConnectorMessages { function parseAddTranche(bytes29 _msg) internal pure - returns (uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, uint128 price) + returns ( + uint64 poolId, + bytes16 trancheId, + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + 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)); + decimals = uint8(_msg.indexUint(185, 1)); + price = uint128(_msg.indexUint(186, 16)); } /** @@ -160,7 +207,7 @@ library ConnectorMessages { } /** - * Update token price + * Update a Tranche token's price * * 0: call type (uint8 = 1 byte) * 1-8: poolId (uint64 = 8 bytes) @@ -193,18 +240,17 @@ library ConnectorMessages { * Transfer Message - Transfer stable coins * * 0: call type (uint8 = 1 byte) - * 1-16: token (uint128 = 16 bytes) + * 1-16: currency (uint128 = 16 bytes) * 17-48: sender address (32 bytes) * 49-80: receiver address (32 bytes) * 81-96: amount (uint128 = 16 bytes) */ - // todo(nuno): we probably need to include the domain - function formatTransfer(uint128 token, bytes32 sender, bytes32 receiver, uint128 amount) + function formatTransfer(uint128 currency, bytes32 sender, bytes32 receiver, uint128 amount) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.Transfer), token, sender, receiver, amount); + return abi.encodePacked(uint8(Call.Transfer), currency, sender, receiver, amount); } function isTransfer(bytes29 _msg) internal pure returns (bool) { @@ -214,58 +260,64 @@ library ConnectorMessages { function parseTransfer(bytes29 _msg) internal pure - returns (uint128 token, bytes32 sender, bytes32 receiver, uint128 amount) + returns (uint128 currency, bytes32 sender, bytes32 receiver, uint128 amount) { - token = uint128(_msg.indexUint(1, 16)); + currency = uint128(_msg.indexUint(1, 16)); sender = bytes32(_msg.index(17, 32)); receiver = bytes32(_msg.index(49, 32)); amount = uint128(_msg.indexUint(81, 16)); } + // An optimised `parseTransfer` function that saves gas by ignoring the `sender` field and that + // parses and returns the `recipient` as an `address` instead of the `bytes32` the message holds. + function parseIncomingTransfer(bytes29 _msg) + internal + pure + returns (uint128 currency, address recipient, uint128 amount) + { + currency = uint128(_msg.indexUint(1, 16)); + recipient = address(bytes20(_msg.index(49, 20))); + amount = uint128(_msg.indexUint(81, 16)); + } + /** * TransferTrancheTokens * * 0: call type (uint8 = 1 byte) * 1-8: poolId (uint64 = 8 bytes) * 9-24: trancheId (16 bytes) - * 25-33: destinationDomain (Domain = 9 bytes) - * 34-65: destinationChainId (uint256 = 32 bytes) - * 66-73: destinationAddress (32 bytes - Either a Centrifuge chain address or an EVM address followed by 12 zeros) - * 74-89: amount (uint128 = 16 bytes) + * 25-56: sender (bytes32) + * 57-65: destinationDomain ((Domain: u8, ChainId: u64) = 9 bytes total) + * 66-97: destinationAddress (32 bytes - Either a Centrifuge chain address or an EVM address followed by 12 zeros) + * 98-113: amount (uint128 = 16 bytes) */ function formatTransferTrancheTokens( uint64 poolId, bytes16 trancheId, + bytes32 sender, bytes9 destinationDomain, - uint256 destinationChainId, bytes32 destinationAddress, uint128 amount ) internal pure returns (bytes memory) { return abi.encodePacked( - uint8(Call.TransferTrancheTokens), - poolId, - trancheId, - destinationDomain, - destinationChainId, - destinationAddress, - amount + uint8(Call.TransferTrancheTokens), poolId, trancheId, sender, destinationDomain, destinationAddress, amount ); } - // Format a TransferTrancheTokens to an EVM domain + // Overload: Format a TransferTrancheTokens to an EVM domain // Note: This is an overload function to dry the cast from `address` to `bytes32` // for the `destinationAddress` field by using the default `formatTransferTrancheTokens` implementation // by appending 12 zeros to the evm-based `destinationAddress`. function formatTransferTrancheTokens( uint64 poolId, bytes16 trancheId, + bytes32 sender, bytes9 destinationDomain, - uint256 destinationChainId, address destinationAddress, uint128 amount ) internal pure returns (bytes memory) { return formatTransferTrancheTokens( - poolId, trancheId, destinationDomain, destinationChainId, bytes32(bytes20(destinationAddress)), amount + poolId, trancheId, sender, destinationDomain, bytes32(bytes20(destinationAddress)), amount ); } @@ -280,43 +332,33 @@ library ConnectorMessages { returns ( uint64 poolId, bytes16 trancheId, + bytes32 sender, bytes9 encodedDomain, - uint256 destinationChainId, bytes32 destinationAddress, uint128 amount ) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); - encodedDomain = bytes9(_msg.index(25, 9)); - destinationChainId = uint256(_msg.indexUint(34, 32)); + sender = bytes32(_msg.index(25, 32)); + encodedDomain = bytes9(_msg.index(57, 9)); destinationAddress = bytes32(_msg.index(66, 32)); amount = uint128(_msg.indexUint(98, 16)); } - // Parse a TransferTrancheTokens to an EVM-based `destinationAddress` (20-byte long) + // Parse a TransferTrancheTokens to an EVM-based `destinationAddress` (20-byte long). + // We ignore the `sender` and the `domain` since it's not relevant when parsing an incoming message. function parseTransferTrancheTokens20(bytes29 _msg) internal pure - returns ( - uint64 poolId, - bytes16 trancheId, - bytes9 encodedDomain, - uint256 destinationChainId, - address destinationAddress, - uint128 amount - ) + returns (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) { - ( - uint64 poolId_, - bytes16 trancheId_, - bytes9 encodedDomain_, - uint256 destinationChainId_, - bytes32 destinationAddress32_, - uint128 amount_ - ) = parseTransferTrancheTokens32(_msg); - destinationAddress = address(bytes20(destinationAddress32_)); - return (poolId_, trancheId_, encodedDomain_, destinationChainId_, destinationAddress, amount_); + poolId = uint64(_msg.indexUint(1, 8)); + trancheId = bytes16(_msg.index(9, 16)); + // ignore: `sender` at bytes 25-56 + // ignore: `domain` at bytes 57-65 + destinationAddress = address(bytes20(_msg.index(66, 20))); + amount = uint128(_msg.indexUint(98, 16)); } /* @@ -326,17 +368,17 @@ library ConnectorMessages { * 1-8: poolId (uint64 = 8 bytes) * 9-24: trancheId (16 bytes) * 25-56: investor address (32 bytes) - * 57-72: token (uint128 = 16 bytes) + * 57-72: currency (uint128 = 16 bytes) * 73-89: amount (uint128 = 16 bytes) */ function formatIncreaseInvestOrder( uint64 poolId, bytes16 trancheId, bytes32 investor, - uint128 token, + uint128 currency, uint128 amount ) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.IncreaseInvestOrder), poolId, trancheId, investor, token, amount); + return abi.encodePacked(uint8(Call.IncreaseInvestOrder), poolId, trancheId, investor, currency, amount); } function isIncreaseInvestOrder(bytes29 _msg) internal pure returns (bool) { @@ -346,12 +388,12 @@ library ConnectorMessages { function parseIncreaseInvestOrder(bytes29 _msg) internal pure - returns (uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 token, uint128 amount) + returns (uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 currency, uint128 amount) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); investor = bytes32(_msg.index(25, 32)); - token = uint128(_msg.indexUint(57, 16)); + currency = uint128(_msg.indexUint(57, 16)); amount = uint128(_msg.indexUint(73, 16)); } @@ -362,17 +404,17 @@ library ConnectorMessages { * 1-8: poolId (uint64 = 8 bytes) * 9-24: trancheId (16 bytes) * 25-56: investor address (32 bytes) - * 57-72: token (uint128 = 16 bytes) + * 57-72: currency (uint128 = 16 bytes) * 73-89: amount (uint128 = 16 bytes) */ function formatDecreaseInvestOrder( uint64 poolId, bytes16 trancheId, bytes32 investor, - uint128 token, + uint128 currency, uint128 amount ) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.DecreaseInvestOrder), poolId, trancheId, investor, token, amount); + return abi.encodePacked(uint8(Call.DecreaseInvestOrder), poolId, trancheId, investor, currency, amount); } function isDecreaseInvestOrder(bytes29 _msg) internal pure returns (bool) { @@ -382,7 +424,7 @@ library ConnectorMessages { function parseDecreaseInvestOrder(bytes29 _msg) internal pure - returns (uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 token, uint128 amount) + returns (uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 currency, uint128 amount) { return parseIncreaseInvestOrder(_msg); } @@ -394,17 +436,17 @@ library ConnectorMessages { * 1-8: poolId (uint64 = 8 bytes) * 9-24: trancheId (16 bytes) * 25-56: investor address (32 bytes) - * 57-72: token (uint128 = 16 bytes) + * 57-72: currency (uint128 = 16 bytes) * 73-89: amount (uint128 = 16 bytes) */ function formatIncreaseRedeemOrder( uint64 poolId, bytes16 trancheId, bytes32 investor, - uint128 token, + uint128 currency, uint128 amount ) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.IncreaseRedeemOrder), poolId, trancheId, investor, token, amount); + return abi.encodePacked(uint8(Call.IncreaseRedeemOrder), poolId, trancheId, investor, currency, amount); } function isIncreaseRedeemOrder(bytes29 _msg) internal pure returns (bool) { @@ -414,7 +456,7 @@ library ConnectorMessages { function parseIncreaseRedeemOrder(bytes29 _msg) internal pure - returns (uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 token, uint128 amount) + returns (uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 currency, uint128 amount) { return parseIncreaseInvestOrder(_msg); } @@ -426,17 +468,17 @@ library ConnectorMessages { * 1-8: poolId (uint64 = 8 bytes) * 9-24: trancheId (16 bytes) * 25-56: investor address (32 bytes) - * 57-72: token (uint128 = 16 bytes) + * 57-72: currency (uint128 = 16 bytes) * 73-89: amount (uint128 = 16 bytes) */ function formatDecreaseRedeemOrder( uint64 poolId, bytes16 trancheId, bytes32 investor, - uint128 token, + uint128 currency, uint128 amount ) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.DecreaseRedeemOrder), poolId, trancheId, investor, token, amount); + return abi.encodePacked(uint8(Call.DecreaseRedeemOrder), poolId, trancheId, investor, currency, amount); } function isDecreaseRedeemOrder(bytes29 _msg) internal pure returns (bool) { @@ -446,117 +488,69 @@ library ConnectorMessages { function parseDecreaseRedeemOrder(bytes29 _msg) internal pure - returns (uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 token, uint128 amount) + returns (uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 currency, uint128 amount) { return parseDecreaseInvestOrder(_msg); } /* - * CollectRedeem Message - * - * 0: call type (uint8 = 1 byte) - * 1-8: poolId (uint64 = 8 bytes) - * 9-24: trancheId (16 bytes) - * 25-56: user address (32 bytes) - */ - function formatCollectRedeem(uint64 poolId, bytes16 trancheId, bytes32 user) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.CollectRedeem), poolId, trancheId, user); - } - - function isCollectRedeem(bytes29 _msg) internal pure returns (bool) { - return messageType(_msg) == Call.CollectRedeem; - } - - function parseCollectRedeem(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, bytes32 user) { - poolId = uint64(_msg.indexUint(1, 8)); - trancheId = bytes16(_msg.index(9, 16)); - user = bytes32(_msg.index(25, 32)); - } - - /* - * CollectForRedeem Message + * CollectInvest Message * * 0: call type (uint8 = 1 byte) * 1-8: poolId (uint64 = 8 bytes) * 9-24: trancheId (16 bytes) - * 25-56: caller address (32 bytes) - * 57-89: user address (32 bytes) + * 25-56: investor address (32 bytes) */ - function formatCollectForRedeem(uint64 poolId, bytes16 trancheId, bytes32 caller, bytes32 user) + function formatCollectInvest(uint64 poolId, bytes16 trancheId, bytes32 investor) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.CollectForRedeem), poolId, trancheId, caller, user); + return abi.encodePacked(uint8(Call.CollectInvest), poolId, trancheId, investor); } - function isCollectForRedeem(bytes29 _msg) internal pure returns (bool) { - return messageType(_msg) == Call.CollectForRedeem; + function isCollectInvest(bytes29 _msg) internal pure returns (bool) { + return messageType(_msg) == Call.CollectInvest; } - function parseCollectForRedeem(bytes29 _msg) + function parseCollectInvest(bytes29 _msg) internal pure - returns (uint64 poolId, bytes16 trancheId, bytes32 caller, bytes32 user) + returns (uint64 poolId, bytes16 trancheId, bytes32 investor) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); - caller = bytes32(_msg.index(25, 32)); - user = bytes32(_msg.index(57, 32)); - } - - /* - * CollectInvest Message - * - * 0: call type (uint8 = 1 byte) - * 1-8: poolId (uint64 = 8 bytes) - * 9-24: trancheId (16 bytes) - * 25-56: user address (32 bytes) - */ - function formatCollectInvest(uint64 poolId, bytes16 trancheId, bytes32 user) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.CollectInvest), poolId, trancheId, user); - } - - function isCollectInvest(bytes29 _msg) internal pure returns (bool) { - return messageType(_msg) == Call.CollectInvest; - } - - function parseCollectInvest(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, bytes32 user) { - poolId = uint64(_msg.indexUint(1, 8)); - trancheId = bytes16(_msg.index(9, 16)); - user = bytes32(_msg.index(25, 32)); + investor = bytes32(_msg.index(25, 32)); } /* - * CollectForInvest Message + * CollectRedeem Message * * 0: call type (uint8 = 1 byte) * 1-8: poolId (uint64 = 8 bytes) * 9-24: trancheId (16 bytes) - * 25-56: caller address (32 bytes) - * 57-89: user address (32 bytes) + * 25-56: investor address (32 bytes) */ - function formatCollectForInvest(uint64 poolId, bytes16 trancheId, bytes32 caller, bytes32 user) + function formatCollectRedeem(uint64 poolId, bytes16 trancheId, bytes32 investor) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.CollectForInvest), poolId, trancheId, caller, user); + return abi.encodePacked(uint8(Call.CollectRedeem), poolId, trancheId, investor); } - function isCollectForInvest(bytes29 _msg) internal pure returns (bool) { - return messageType(_msg) == Call.CollectForInvest; + function isCollectRedeem(bytes29 _msg) internal pure returns (bool) { + return messageType(_msg) == Call.CollectRedeem; } - function parseCollectForInvest(bytes29 _msg) + function parseCollectRedeem(bytes29 _msg) internal pure - returns (uint64 poolId, bytes16 trancheId, bytes32 caller, bytes32 user) + returns (uint64 poolId, bytes16 trancheId, bytes32 investor) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); - caller = bytes32(_msg.index(25, 32)); - user = bytes32(_msg.index(57, 32)); + investor = bytes32(_msg.index(25, 32)); } // Utils @@ -565,8 +559,8 @@ library ConnectorMessages { return bytes9(bytes1(uint8(domain))); } - function formatDomain(Domain domain, uint64 domainId) public pure returns (bytes9) { - return bytes9(abi.encodePacked(uint8(domain), domainId).ref(0).index(0, 9)); + function formatDomain(Domain domain, uint64 chainId) public pure returns (bytes9) { + return bytes9(abi.encodePacked(uint8(domain), chainId).ref(0).index(0, 9)); } // TODO: should be moved to a util contract diff --git a/src/routers/Gateway.sol b/src/routers/Gateway.sol index 1156358b..0983aad8 100644 --- a/src/routers/Gateway.sol +++ b/src/routers/Gateway.sol @@ -6,23 +6,22 @@ import {TypedMemView} from "memview-sol/TypedMemView.sol"; import {ConnectorMessages} from "../Messages.sol"; interface ConnectorLike { - function addPool(uint64 poolId, uint128 currency, uint8 decimals) external; + function addCurrency(uint128 currency, address currencyAddress) external; + function addPool(uint64 poolId) external; + function allowPoolCurrency(uint64 poolId, uint128 currency) external; function addTranche( uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price ) external; function updateMember(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) external; function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint128 price) external; - function handleTransferTrancheTokens( - uint64 poolId, - bytes16 trancheId, - uint256 destinationChainId, - address destinationAddress, - uint128 amount - ) external; + function handleTransfer(uint128 currency, address recipient, uint128 amount) external; + function handleTransferTrancheTokens(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) + external; } interface RouterLike { @@ -84,6 +83,7 @@ contract ConnectorGateway { function transferTrancheTokensToCentrifuge( uint64 poolId, bytes16 trancheId, + address sender, bytes32 destinationAddress, uint128 amount ) public onlyConnector { @@ -91,8 +91,8 @@ contract ConnectorGateway { ConnectorMessages.formatTransferTrancheTokens( poolId, trancheId, + addressToBytes32(sender), ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge), - uint256(0), destinationAddress, amount ) @@ -102,7 +102,8 @@ contract ConnectorGateway { function transferTrancheTokensToEVM( uint64 poolId, bytes16 trancheId, - uint256 destinationChainId, + address sender, + uint64 destinationChainId, address destinationAddress, uint128 amount ) public onlyConnector { @@ -110,28 +111,85 @@ contract ConnectorGateway { ConnectorMessages.formatTransferTrancheTokens( poolId, trancheId, - ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM), - destinationChainId, + addressToBytes32(sender), + ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, destinationChainId), destinationAddress, amount ) ); } - function transfer(uint128 token, bytes32 sender, bytes32 receiver, uint128 amount) public onlyConnector { - router.send(ConnectorMessages.formatTransfer(token, sender, receiver, amount)); + function transfer(uint128 token, address sender, bytes32 receiver, uint128 amount) public onlyConnector { + router.send(ConnectorMessages.formatTransfer(token, addressToBytes32(sender), receiver, amount)); + } + + function increaseInvestOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + public + onlyConnector + { + router.send( + ConnectorMessages.formatIncreaseInvestOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) + ); + } + + function decreaseInvestOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + public + onlyConnector + { + router.send( + ConnectorMessages.formatDecreaseInvestOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) + ); + } + + function increaseRedeemOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + public + onlyConnector + { + router.send( + ConnectorMessages.formatIncreaseRedeemOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) + ); + } + + function decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + public + onlyConnector + { + router.send( + ConnectorMessages.formatDecreaseRedeemOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) + ); + } + + function collectInvest(uint64 poolId, bytes16 trancheId, address investor) public onlyConnector { + router.send(ConnectorMessages.formatCollectInvest(poolId, trancheId, addressToBytes32(investor))); + } + + function collectRedeem(uint64 poolId, bytes16 trancheId, address investor) public onlyConnector { + router.send(ConnectorMessages.formatCollectRedeem(poolId, trancheId, addressToBytes32(investor))); } // --- Incoming --- function handle(bytes memory _message) external onlyRouter { bytes29 _msg = _message.ref(0); - if (ConnectorMessages.isAddPool(_msg)) { - (uint64 poolId, uint128 currency, uint8 decimals) = ConnectorMessages.parseAddPool(_msg); - connector.addPool(poolId, currency, decimals); + + if (ConnectorMessages.isAddCurrency(_msg)) { + (uint128 currency, address currencyAddress) = ConnectorMessages.parseAddCurrency(_msg); + connector.addCurrency(currency, currencyAddress); + } else if (ConnectorMessages.isAddPool(_msg)) { + (uint64 poolId) = ConnectorMessages.parseAddPool(_msg); + connector.addPool(poolId); + } else if (ConnectorMessages.isAllowPoolCurrency(_msg)) { + (uint64 poolId, uint128 currency) = ConnectorMessages.parseAllowPoolCurrency(_msg); + connector.allowPoolCurrency(poolId, currency); } 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); + ( + uint64 poolId, + bytes16 trancheId, + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + uint128 price + ) = ConnectorMessages.parseAddTranche(_msg); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); } else if (ConnectorMessages.isUpdateMember(_msg)) { (uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) = ConnectorMessages.parseUpdateMember(_msg); @@ -139,12 +197,20 @@ contract ConnectorGateway { } else if (ConnectorMessages.isUpdateTrancheTokenPrice(_msg)) { (uint64 poolId, bytes16 trancheId, uint128 price) = ConnectorMessages.parseUpdateTrancheTokenPrice(_msg); connector.updateTokenPrice(poolId, trancheId, price); + } else if (ConnectorMessages.isTransfer(_msg)) { + (uint128 currency, address recipient, uint128 amount) = ConnectorMessages.parseIncomingTransfer(_msg); + connector.handleTransfer(currency, recipient, amount); } else if (ConnectorMessages.isTransferTrancheTokens(_msg)) { - (uint64 poolId, bytes16 trancheId,, uint256 destinationChainId, address destinationAddress, uint128 amount) - = ConnectorMessages.parseTransferTrancheTokens20(_msg); - connector.handleTransferTrancheTokens(poolId, trancheId, destinationChainId, destinationAddress, amount); + (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) = + ConnectorMessages.parseTransferTrancheTokens20(_msg); + connector.handleTransferTrancheTokens(poolId, trancheId, destinationAddress, amount); } else { revert("ConnectorGateway/invalid-message"); } } + + // Utils + function addressToBytes32(address x) private pure returns (bytes32) { + return bytes32(bytes20(x)); + } } diff --git a/src/routers/axelar/Router.sol b/src/routers/axelar/Router.sol index 67676bc7..57f61217 100644 --- a/src/routers/axelar/Router.sol +++ b/src/routers/axelar/Router.sol @@ -9,6 +9,7 @@ interface ConnectorLike { bytes16 trancheId, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price ) external; function updateMember(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) external; diff --git a/src/token/restricted.sol b/src/token/restricted.sol index 5ea43195..ce64a377 100644 --- a/src/token/restricted.sol +++ b/src/token/restricted.sol @@ -12,6 +12,7 @@ interface ERC20Like { function mint(address user, uint256 value) external; function name() external view returns (string memory); function symbol() external view returns (string memory); + function decimals() external view returns (uint8); function balanceOf(address user) external view returns (uint256 value); function burn(address user, uint256 value) external; function transfer(address to, uint256 amount) external returns (bool); diff --git a/test/Connector.t.sol b/test/Connector.t.sol index b8a815e9..45ae68b6 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -7,6 +7,7 @@ import {ConnectorGateway} from "src/routers/Gateway.sol"; import {ConnectorEscrow} from "src/Escrow.sol"; import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol"; import {RestrictedTokenLike} from "src/token/restricted.sol"; +import {ERC20} from "src/token/erc20.sol"; import {MemberlistLike, Memberlist} from "src/token/memberlist.sol"; import {MockHomeConnector} from "./mock/MockHomeConnector.sol"; import {MockXcmRouter} from "./mock/MockXcmRouter.sol"; @@ -14,6 +15,11 @@ import {ConnectorMessages} from "../src/Messages.sol"; import "forge-std/Test.sol"; import "../src/Connector.sol"; +interface EscrowLike_ { + function approve(address token, address spender, uint256 value) external; + function rely(address usr) external; +} + contract ConnectorTest is Test { CentrifugeConnector bridgedConnector; ConnectorGateway gateway; @@ -33,45 +39,88 @@ contract ConnectorTest is Test { connector = new MockHomeConnector(address(mockXcmRouter)); gateway = new ConnectorGateway(address(bridgedConnector), address(mockXcmRouter)); bridgedConnector.file("gateway", address(gateway)); + EscrowLike_(escrow_).rely(address(bridgedConnector)); mockXcmRouter.file("gateway", address(gateway)); } - function testAddingPoolWorks(uint64 poolId, uint128 currency, uint8 decimals) public { - connector.addPool(poolId, currency, decimals); - (uint64 actualPoolId,,) = bridgedConnector.pools(poolId); + function testAddCurrencyWorks(uint128 currency, uint128 badCurrency) public { + vm.assume(currency > 0); + vm.assume(badCurrency > 0); + vm.assume(currency != badCurrency); + + ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + connector.addCurrency(currency, address(erc20)); + (address address_) = bridgedConnector.currencyIdToAddress(currency); + assertEq(address_, address(erc20)); + + // Verify we can't override the same currency id another address + ERC20 badErc20 = newErc20("BadActor's Dollar", "BADUSD", 66); + vm.expectRevert(bytes("CentrifugeConnector/currency-id-in-use")); + connector.addCurrency(currency, address(badErc20)); + assertEq(bridgedConnector.currencyIdToAddress(currency), address(erc20)); + + // Verify we can't add a currency address that already exists associated with a different currency id + vm.expectRevert(bytes("CentrifugeConnector/currency-address-in-use")); + connector.addCurrency(badCurrency, address(erc20)); + assertEq(bridgedConnector.currencyIdToAddress(currency), address(erc20)); + } + + function testAddPoolWorks(uint64 poolId) public { + connector.addPool(poolId); + (uint64 actualPoolId,) = bridgedConnector.pools(poolId); assertEq(uint256(actualPoolId), uint256(poolId)); } - function testAddingPoolMultipleTimesFails(uint64 poolId, uint128 currency, uint8 decimals) public { - connector.addPool(poolId, currency, decimals); + function testAllowPoolCurrencyWorks(uint128 currency, uint64 poolId) public { + ERC20 token = newErc20("X's Dollar", "USDX", 42); + connector.addCurrency(currency, address(token)); + connector.addPool(poolId); + + connector.allowPoolCurrency(poolId, currency); + assertTrue(bridgedConnector.allowedPoolCurrencies(poolId, address(token))); + } + + function testAllowPoolCurrencyWithUnknownCurrencyFails(uint128 currency, uint64 poolId) public { + connector.addPool(poolId); + vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + connector.allowPoolCurrency(poolId, currency); + } + + function testAddingPoolMultipleTimesFails(uint64 poolId) public { + connector.addPool(poolId); vm.expectRevert(bytes("CentrifugeConnector/pool-already-added")); - connector.addPool(poolId, currency, decimals); + connector.addPool(poolId); } - function testAddingPoolAsNonRouterFails(uint64 poolId, uint128 currency, uint8 decimals) public { + function testAddingPoolAsNonRouterFails(uint64 poolId) public { vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); - bridgedConnector.addPool(poolId, currency, decimals); + bridgedConnector.addPool(poolId); } function testAddingSingleTrancheWorks( uint64 poolId, - uint128 currency, - uint8 decimals, + bytes16 trancheId, string memory tokenName, string memory tokenSymbol, - bytes16 trancheId, + uint8 decimals, uint128 price ) public { - connector.addPool(poolId, currency, decimals); - (uint64 actualPoolId,,) = bridgedConnector.pools(poolId); + connector.addPool(poolId); + (uint64 actualPoolId,) = bridgedConnector.pools(poolId); assertEq(uint256(actualPoolId), uint256(poolId)); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); bridgedConnector.deployTranche(poolId, trancheId); - (address token_, uint256 latestPrice,, string memory actualTokenName, string memory actualTokenSymbol) = - bridgedConnector.tranches(poolId, trancheId); + ( + address token_, + uint256 latestPrice, + , + string memory actualTokenName, + string memory actualTokenSymbol, + uint8 actualDecimals + ) = bridgedConnector.tranches(poolId, trancheId); assertTrue(token_ != address(0)); assertEq(latestPrice, price); @@ -81,62 +130,63 @@ contract ConnectorTest is Test { // is used to simulate this intended behaviour. assertEq(actualTokenName, bytes32ToString(stringToBytes32(tokenName))); assertEq(actualTokenSymbol, bytes32ToString(stringToBytes32(tokenSymbol))); + assertEq(actualDecimals, decimals); RestrictedTokenLike token = RestrictedTokenLike(token_); assertEq(token.name(), bytes32ToString(stringToBytes32(tokenName))); assertEq(token.symbol(), bytes32ToString(stringToBytes32(tokenSymbol))); + assertEq(token.decimals(), decimals); } function testAddingTrancheMultipleTimesFails( uint64 poolId, - uint128 currency, uint8 decimals, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 price ) public { - connector.addPool(poolId, currency, decimals); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); vm.expectRevert(bytes("CentrifugeConnector/tranche-already-added")); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); } function testAddingMultipleTranchesWorks( uint64 poolId, - uint128 currency, - uint8 decimals, bytes16[] calldata trancheIds, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price ) public { vm.assume(trancheIds.length > 0 && trancheIds.length < 5); vm.assume(!hasDuplicates(trancheIds)); - connector.addPool(poolId, currency, decimals); + connector.addPool(poolId); for (uint256 i = 0; i < trancheIds.length; i++) { - connector.addTranche(poolId, trancheIds[i], tokenName, tokenSymbol, price); + connector.addTranche(poolId, trancheIds[i], tokenName, tokenSymbol, decimals, price); bridgedConnector.deployTranche(poolId, trancheIds[i]); - (address token, uint256 latestPrice,,,) = bridgedConnector.tranches(poolId, trancheIds[i]); + (address token, uint256 latestPrice,,,, uint8 actualDecimals) = + bridgedConnector.tranches(poolId, trancheIds[i]); assertEq(latestPrice, price); assertTrue(token != address(0)); + assertEq(actualDecimals, decimals); } } function testAddingTranchesAsNonRouterFails( uint64 poolId, - uint128 currency, - uint8 decimals, bytes16 trancheId, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price ) public { - connector.addPool(poolId, currency, decimals); + connector.addPool(poolId); vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); - bridgedConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + bridgedConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); } function testAddingTranchesForNonExistentPoolFails( @@ -144,23 +194,23 @@ contract ConnectorTest is Test { bytes16 trancheId, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price ) public { vm.expectRevert(bytes("CentrifugeConnector/invalid-pool")); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); } function testDeployingTrancheMultipleTimesFails( uint64 poolId, - uint128 currency, uint8 decimals, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 price ) public { - connector.addPool(poolId, currency, decimals); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); bridgedConnector.deployTranche(poolId, trancheId); vm.expectRevert(bytes("CentrifugeConnector/tranche-already-deployed")); @@ -169,7 +219,6 @@ contract ConnectorTest is Test { function testDeployingWrongTrancheFails( uint64 poolId, - uint128 currency, uint8 decimals, string memory tokenName, string memory tokenSymbol, @@ -179,18 +228,17 @@ contract ConnectorTest is Test { ) public { vm.assume(trancheId != wrongTrancheId); - connector.addPool(poolId, currency, decimals); - (uint64 actualPoolId,,) = bridgedConnector.pools(poolId); + connector.addPool(poolId); + (uint64 actualPoolId,) = bridgedConnector.pools(poolId); assertEq(uint256(actualPoolId), uint256(poolId)); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); bridgedConnector.deployTranche(poolId, wrongTrancheId); } function testDeployingTrancheOnNonExistentPoolFails( uint64 poolId, - uint128 currency, uint8 decimals, uint64 wrongPoolId, string memory tokenName, @@ -200,32 +248,27 @@ contract ConnectorTest is Test { ) public { vm.assume(poolId != wrongPoolId); - connector.addPool(poolId, currency, decimals); - (uint64 actualPoolId,,) = bridgedConnector.pools(poolId); + connector.addPool(poolId); + (uint64 actualPoolId,) = bridgedConnector.pools(poolId); assertEq(uint256(actualPoolId), uint256(poolId)); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); bridgedConnector.deployTranche(wrongPoolId, trancheId); } - function testUpdatingMemberWorks( - uint64 poolId, - uint128 currency, - uint8 decimals, - bytes16 trancheId, - address user, - uint64 validUntil - ) public { + function testUpdatingMemberWorks(uint64 poolId, uint8 decimals, bytes16 trancheId, address user, uint64 validUntil) + public + { vm.assume(validUntil >= block.timestamp); vm.assume(user != address(0)); - connector.addPool(poolId, currency, decimals); - connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", 123); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", decimals, 123); bridgedConnector.deployTranche(poolId, trancheId); connector.updateMember(poolId, trancheId, user, validUntil); - (address token_,,,,) = bridgedConnector.tranches(poolId, trancheId); + (address token_,,,,,) = bridgedConnector.tranches(poolId, trancheId); RestrictedTokenLike token = RestrictedTokenLike(token_); assertTrue(token.hasMember(user)); @@ -257,43 +300,32 @@ contract ConnectorTest is Test { function testUpdatingMemberForNonExistentTrancheFails( uint64 poolId, - uint128 currency, - uint8 decimals, bytes16 trancheId, address user, uint64 validUntil ) public { vm.assume(validUntil > block.timestamp); - connector.addPool(poolId, currency, decimals); + connector.addPool(poolId); + vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); connector.updateMember(poolId, trancheId, user, validUntil); } - function testUpdatingTokenPriceWorks( - uint64 poolId, - uint128 currency, - uint8 decimals, - bytes16 trancheId, - uint128 price - ) public { - connector.addPool(poolId, currency, decimals); - connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", 123); + function testUpdatingTokenPriceWorks(uint64 poolId, uint8 decimals, bytes16 trancheId, uint128 price) public { + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", decimals, 123); connector.updateTokenPrice(poolId, trancheId, price); - (, uint256 latestPrice, uint256 lastPriceUpdate,,) = bridgedConnector.tranches(poolId, trancheId); + (, uint256 latestPrice, uint256 lastPriceUpdate,,,) = bridgedConnector.tranches(poolId, trancheId); assertEq(latestPrice, price); assertEq(lastPriceUpdate, block.timestamp); } - function testUpdatingTokenPriceAsNonRouterFails( - uint64 poolId, - uint128 currency, - uint8 decimals, - bytes16 trancheId, - uint128 price - ) public { - connector.addPool(poolId, currency, decimals); - connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", 123); + function testUpdatingTokenPriceAsNonRouterFails(uint64 poolId, uint8 decimals, bytes16 trancheId, uint128 price) + public + { + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", decimals, 123); vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); bridgedConnector.updateTokenPrice(poolId, trancheId, price); } @@ -304,23 +336,101 @@ contract ConnectorTest is Test { bridgedConnector.updateTokenPrice(poolId, trancheId, price); } - function testUpdatingTokenPriceForNonExistentTrancheFails( - uint64 poolId, + function testUpdatingTokenPriceForNonExistentTrancheFails(uint64 poolId, bytes16 trancheId, uint128 price) public { + connector.addPool(poolId); + + vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); + connector.updateTokenPrice(poolId, trancheId, price); + } + + function testIncomingTransferWithoutEscrowFundsFails( + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, uint128 currency, + bytes32 sender, + address recipient, + uint128 amount + ) public { + vm.assume(decimals > 0); + vm.assume(amount > 0); + vm.assume(recipient != address(0)); + + ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); + connector.addCurrency(currency, address(erc20)); + + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + vm.expectRevert(bytes("ERC20/insufficient-balance")); + connector.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + assertEq(erc20.balanceOf(recipient), 0); + } + + function testIncomingTransferWorks( + string memory tokenName, + string memory tokenSymbol, uint8 decimals, - bytes16 trancheId, - uint128 price + uint128 currency, + bytes32 sender, + address recipient, + uint128 amount ) public { - connector.addPool(poolId, currency, decimals); - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); - connector.updateTokenPrice(poolId, trancheId, price); + vm.assume(decimals > 0); + vm.assume(amount > 0); + vm.assume(currency != 0); + vm.assume(recipient != address(0)); + + ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); + connector.addCurrency(currency, address(erc20)); + + // First, an outgoing transfer must take place which has funds currency of the currency moved to + // the escrow account, from which funds are moved from into the recipient on an incoming transfer. + erc20.approve(address(bridgedConnector), type(uint256).max); + erc20.mint(address(this), amount); + bridgedConnector.transfer(address(erc20), bytes32(bytes20(recipient)), amount); + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), amount); + + // Now we test the incoming message + connector.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + assertEq(erc20.balanceOf(recipient), amount); } + // Verify that funds are moved from the msg.sender into the escrow account + function testOutgoingTransferWorks( + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + uint128 initialBalance, + uint128 currency, + bytes32 recipient, + uint128 amount + ) public { + vm.assume(decimals > 0); + vm.assume(amount > 0); + vm.assume(currency != 0); + vm.assume(initialBalance >= amount); + + ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); + + vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + bridgedConnector.transfer(address(erc20), recipient, amount); + connector.addCurrency(currency, address(erc20)); + + erc20.mint(address(this), initialBalance); + assertEq(erc20.balanceOf(address(this)), initialBalance); + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + erc20.approve(address(bridgedConnector), type(uint256).max); + + bridgedConnector.transfer(address(erc20), recipient, amount); + assertEq(erc20.balanceOf(address(this)), initialBalance - amount); + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), amount); + } // Test transferring `amount` to the address(this)'s account (Centrifuge Chain -> EVM like) and then try // transferring that amount to a `centChainAddress` (EVM -> Centrifuge Chain like). + function testTransferTrancheTokensToCentrifuge( uint64 poolId, - uint128 currency, uint8 decimals, string memory tokenName, string memory tokenSymbol, @@ -331,16 +441,16 @@ contract ConnectorTest is Test { uint64 validUntil ) public { vm.assume(validUntil > block.timestamp + 7 days); - connector.addPool(poolId, currency, decimals); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); bridgedConnector.deployTranche(poolId, trancheId); connector.updateMember(poolId, trancheId, address(this), validUntil); // fund this account with amount - connector.incomingTransfer(poolId, trancheId, 1, address(this), amount); + connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), address(this), amount); // Verify the address(this) has the expected amount - (address tokenAddress,,,,) = bridgedConnector.tranches(poolId, trancheId); + (address tokenAddress,,,,,) = bridgedConnector.tranches(poolId, trancheId); RestrictedTokenLike token = RestrictedTokenLike(tokenAddress); assertEq(token.balanceOf(address(this)), amount); @@ -353,8 +463,8 @@ contract ConnectorTest is Test { bytes memory message = ConnectorMessages.formatTransferTrancheTokens( poolId, trancheId, + bytes32(bytes20(address(this))), ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge), - 0, centChainAddress, amount ); @@ -364,10 +474,9 @@ contract ConnectorTest is Test { function testTransferTrancheTokensFromCentrifuge( uint64 poolId, bytes16 trancheId, - uint128 currency, - uint8 decimals, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price, uint64 validUntil, address destinationAddress, @@ -375,102 +484,312 @@ contract ConnectorTest is Test { ) public { vm.assume(validUntil > block.timestamp + 7 days); vm.assume(destinationAddress != address(0)); - connector.addPool(poolId, currency, decimals); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); bridgedConnector.deployTranche(poolId, trancheId); connector.updateMember(poolId, trancheId, destinationAddress, validUntil); - bytes9 encodedDomain = ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge); - connector.incomingTransfer(poolId, trancheId, 1, destinationAddress, amount); - - (address token,,,,) = bridgedConnector.tranches(poolId, trancheId); + connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), destinationAddress, amount); + (address token,,,,,) = bridgedConnector.tranches(poolId, trancheId); assertEq(ERC20Like(token).balanceOf(destinationAddress), amount); } function testTransferTrancheTokensFromCentrifugeWithoutMemberFails( uint64 poolId, bytes16 trancheId, - uint128 currency, - uint8 decimals, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price, address destinationAddress, uint128 amount ) public { vm.assume(destinationAddress != address(0)); - connector.addPool(poolId, currency, decimals); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); bridgedConnector.deployTranche(poolId, trancheId); - bytes9 encodedDomain = ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM); vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); - connector.incomingTransfer(poolId, trancheId, 1, destinationAddress, amount); + connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), destinationAddress, amount); - (address token,,,,) = bridgedConnector.tranches(poolId, trancheId); + (address token,,,,,) = bridgedConnector.tranches(poolId, trancheId); assertEq(ERC20Like(token).balanceOf(destinationAddress), 0); } function testTransferTrancheTokensToEVM( uint64 poolId, bytes16 trancheId, - uint128 currency, - uint8 decimals, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price, uint64 validUntil, - uint64 destinationChainId, address destinationAddress, uint128 amount ) public { vm.assume(validUntil > block.timestamp + 7 days); vm.assume(destinationAddress != address(0)); vm.assume(amount > 0); - connector.addPool(poolId, currency, decimals); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); bridgedConnector.deployTranche(poolId, trancheId); connector.updateMember(poolId, trancheId, destinationAddress, validUntil); connector.updateMember(poolId, trancheId, address(this), validUntil); // Fund this address with amount - connector.incomingTransfer(poolId, trancheId, 1, address(this), amount); - (address token,,,,) = bridgedConnector.tranches(poolId, trancheId); + connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), address(this), amount); + (address token,,,,,) = bridgedConnector.tranches(poolId, trancheId); assertEq(ERC20Like(token).balanceOf(address(this)), amount); - // Approve and transfer amont from this address to destinationAddress + // Approve and transfer amount from this address to destinationAddress ERC20Like(token).approve(address(bridgedConnector), amount); - bridgedConnector.transferTrancheTokensToEVM(poolId, trancheId, 2, destinationAddress, amount); + bridgedConnector.transferTrancheTokensToEVM( + poolId, trancheId, uint64(block.chainid), destinationAddress, amount + ); assertEq(ERC20Like(token).balanceOf(address(this)), 0); } function testIncreaseInvestOrder( uint64 poolId, bytes16 trancheId, - uint128 amount, + string memory trancheTokenName, + string memory trancheTokenSymbol, + uint8 trancheDecimals, + uint128 price, + uint64 validUntil, uint128 currency, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, + uint8 erc20Decimals, + uint128 amount + ) public { + vm.assume(amount > 0); + vm.assume(trancheDecimals & erc20Decimals > 0); + vm.assume(validUntil > block.timestamp + 7 days); + vm.assume(currency != 0); + + ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); + + vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); + bridgedConnector.deployTranche(poolId, trancheId); + + vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); + connector.updateMember(poolId, trancheId, address(this), validUntil); + + vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); + connector.addCurrency(currency, address(erc20)); + + vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); + bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); + connector.allowPoolCurrency(poolId, currency); + + erc20.approve(address(bridgedConnector), type(uint256).max); + erc20.mint(address(this), amount); + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), amount); + assertEq(erc20.balanceOf(address(this)), 0); + } + + function testDecreaseInvestOrder( + uint64 poolId, + bytes16 trancheId, + string memory trancheTokenName, + string memory trancheTokenSymbol, + uint8 trancheDecimals, uint128 price, - uint64 validUntil + uint64 validUntil, + uint128 currency, + uint8 erc20Decimals, + uint128 amount ) public { + vm.assume(amount > 0); + vm.assume(trancheDecimals & erc20Decimals > 0); vm.assume(validUntil > block.timestamp + 7 days); + vm.assume(currency != 0); + + ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); - connector.addPool(poolId, currency, decimals); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price); + vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); bridgedConnector.deployTranche(poolId, trancheId); + + vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); + connector.updateMember(poolId, trancheId, address(this), validUntil); + + vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); + connector.addCurrency(currency, address(erc20)); + + vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); + bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); + connector.allowPoolCurrency(poolId, currency); + + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + assertEq(erc20.balanceOf(address(this)), 0); + bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + assertEq(erc20.balanceOf(address(this)), 0); + } + + function testIncreaseRedeemOrder( + uint64 poolId, + bytes16 trancheId, + string memory trancheTokenName, + string memory trancheTokenSymbol, + uint8 trancheDecimals, + uint128 price, + uint64 validUntil, + uint128 currency, + uint8 erc20Decimals, + uint128 amount + ) public { + vm.assume(amount > 0); + vm.assume(trancheDecimals & erc20Decimals > 0); + vm.assume(validUntil > block.timestamp + 7 days); + vm.assume(currency != 0); + + ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); + + vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); + bridgedConnector.deployTranche(poolId, trancheId); + + vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); connector.updateMember(poolId, trancheId, address(this), validUntil); - // todo(nuno): we need to first agree on the currencyId/address discussion - // and then be able to pass the right param to `addPool`, make sure the - // corresponding currency is a deployed ERC20Like token, mint sufficient - // funds to the right account; then we call bridgedConnector.increaseInvestOrder - // and verified the `amount` was transferred from the caller account into - // the escrow contract. + vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); + connector.addCurrency(currency, address(erc20)); + + vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); + bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); + connector.allowPoolCurrency(poolId, currency); + + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + assertEq(erc20.balanceOf(address(this)), 0); + bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + assertEq(erc20.balanceOf(address(this)), 0); + } + + function testDecreaseRedeemOrder( + uint64 poolId, + bytes16 trancheId, + string memory trancheTokenName, + string memory trancheTokenSymbol, + uint8 trancheDecimals, + uint128 price, + uint64 validUntil, + uint128 currency, + uint8 erc20Decimals, + uint128 amount + ) public { + vm.assume(amount > 0); + vm.assume(trancheDecimals & erc20Decimals > 0); + vm.assume(validUntil > block.timestamp + 7 days); + vm.assume(currency != 0); + + ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); + + vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); + bridgedConnector.deployTranche(poolId, trancheId); + + vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); + connector.updateMember(poolId, trancheId, address(this), validUntil); + + vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); + connector.addCurrency(currency, address(erc20)); + + vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); + bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); + connector.allowPoolCurrency(poolId, currency); + + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + assertEq(erc20.balanceOf(address(this)), 0); + bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); + assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + assertEq(erc20.balanceOf(address(this)), 0); + } + + function testCollectRedeem( + uint64 poolId, + bytes16 trancheId, + string memory trancheTokenName, + string memory trancheTokenSymbol, + uint8 trancheDecimals, + uint128 price, + uint64 validUntil, + uint128 amount + ) public { + vm.assume(amount > 0); + vm.assume(trancheDecimals > 0); + vm.assume(validUntil > block.timestamp + 7 days); + + vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + bridgedConnector.collectRedeem(poolId, trancheId); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); + bridgedConnector.deployTranche(poolId, trancheId); + + vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + bridgedConnector.collectRedeem(poolId, trancheId); + connector.updateMember(poolId, trancheId, address(this), validUntil); + + bridgedConnector.collectRedeem(poolId, trancheId); + } + + function testCollectInvest( + uint64 poolId, + bytes16 trancheId, + string memory trancheTokenName, + string memory trancheTokenSymbol, + uint8 trancheDecimals, + uint128 price, + uint64 validUntil, + uint128 amount + ) public { + vm.assume(amount > 0); + vm.assume(trancheDecimals > 0); + vm.assume(validUntil > block.timestamp + 7 days); + + vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + bridgedConnector.collectInvest(poolId, trancheId); + connector.addPool(poolId); + connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); + bridgedConnector.deployTranche(poolId, trancheId); + + vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + bridgedConnector.collectInvest(poolId, trancheId); + connector.updateMember(poolId, trancheId, address(this), validUntil); + + bridgedConnector.collectInvest(poolId, trancheId); } // helpers + function newErc20(string memory name, string memory symbol, uint8 decimals) internal returns (ERC20) { + ERC20 erc20 = new ERC20(decimals); + erc20.file("name", name); + erc20.file("symbol", symbol); + + return erc20; + } + function stringToBytes32(string memory source) internal pure returns (bytes32 result) { bytes memory tempEmptyStringTest = bytes(source); if (tempEmptyStringTest.length == 0) { diff --git a/test/Invariants.t.sol b/test/Invariants.t.sol index 25275c63..e43d3a6d 100644 --- a/test/Invariants.t.sol +++ b/test/Invariants.t.sol @@ -48,7 +48,7 @@ contract ConnectorInvariants is Test { for (uint256 i = 0; i < poolManager.allTranchesLength(); i++) { bytes16 trancheId = poolManager.allTranches(i); uint64 poolId = poolManager.trancheIdToPoolId(trancheId); - (, uint256 createdAt,) = bridgedConnector.pools(poolId); + (, uint256 createdAt) = bridgedConnector.pools(poolId); assertTrue(createdAt > 0); } } diff --git a/test/Messages.t.sol b/test/Messages.t.sol index 2d52de28..98813729 100644 --- a/test/Messages.t.sol +++ b/test/Messages.t.sol @@ -13,46 +13,70 @@ contract MessagesTest is Test { function setUp() public {} - function testAddPoolEncoding() public { - assertEq( - ConnectorMessages.formatAddPool(0, 0, 0), fromHex("0100000000000000000000000000000000000000000000000000") - ); + function testAddCurrencyEncoding() public { assertEq( - ConnectorMessages.formatAddPool(1, 2, 3), fromHex("0100000000000000010000000000000000000000000000000203") + ConnectorMessages.formatAddCurrency(42, 0x1234567890123456789012345678901234567890), + fromHex("010000000000000000000000000000002a1234567890123456789012345678901234567890") ); - assertEq( - ConnectorMessages.formatAddPool(12378532, 123, 18), - fromHex("010000000000bce1a40000000000000000000000000000007b12") + } + + function testAddCurrencyDecoding() public { + (uint128 currency, address currencyAddress) = ConnectorMessages.parseAddCurrency( + fromHex("010000000000000000000000000000002a1234567890123456789012345678901234567890").ref(0) ); + assertEq(uint256(currency), 42); + assertEq(currencyAddress, 0x1234567890123456789012345678901234567890); + } + + function testAddCurrencyEquivalence(uint128 currency, address currencyAddress) public { + bytes memory _message = ConnectorMessages.formatAddCurrency(currency, currencyAddress); + (uint128 decodedCurrency, address decodedCurrencyAddress) = ConnectorMessages.parseAddCurrency(_message.ref(0)); + assertEq(decodedCurrency, uint256(currency)); + assertEq(decodedCurrencyAddress, currencyAddress); + } + + function testAddPoolEncoding() public { + assertEq(ConnectorMessages.formatAddPool(0), fromHex("020000000000000000")); + assertEq(ConnectorMessages.formatAddPool(1), fromHex("020000000000000001")); + assertEq(ConnectorMessages.formatAddPool(12378532), fromHex("020000000000bce1a4")); } function testAddPoolDecoding() public { - (uint64 actualPoolId1, uint128 actualCurrency1, uint8 actualDecimals1) = - ConnectorMessages.parseAddPool(fromHex("0100000000000000000000000000000000000000000000000000").ref(0)); + (uint64 actualPoolId1) = ConnectorMessages.parseAddPool(fromHex("020000000000000000").ref(0)); assertEq(uint256(actualPoolId1), 0); - assertEq(uint256(actualCurrency1), 0); - assertEq(uint256(actualDecimals1), 0); - (uint64 actualPoolId2, uint128 actualCurrency2, uint8 actualDecimals2) = - ConnectorMessages.parseAddPool(fromHex("0100000000000000010000000000000000000000000000000203").ref(0)); + (uint64 actualPoolId2) = ConnectorMessages.parseAddPool(fromHex("020000000000000001").ref(0)); assertEq(uint256(actualPoolId2), 1); - assertEq(uint256(actualCurrency2), 2); - assertEq(uint256(actualDecimals2), 3); - (uint64 actualPoolId3, uint128 actualCurrency3, uint8 actualDecimals3) = - ConnectorMessages.parseAddPool(fromHex("010000000000bce1a40000000000000000000000000000007b12").ref(0)); + (uint64 actualPoolId3) = ConnectorMessages.parseAddPool(fromHex("020000000000bce1a4").ref(0)); assertEq(uint256(actualPoolId3), 12378532); - assertEq(uint256(actualCurrency3), 123); - assertEq(uint256(actualDecimals3), 18); } - function testAddPoolEquivalence(uint64 poolId, uint128 currency, uint8 decimals) public { - bytes memory _message = ConnectorMessages.formatAddPool(poolId, currency, decimals); - (uint64 decodedPoolId, uint128 decodedCurrency, uint8 decodedDecimals) = - ConnectorMessages.parseAddPool(_message.ref(0)); + function testAddPoolEquivalence(uint64 poolId) public { + bytes memory _message = ConnectorMessages.formatAddPool(poolId); + (uint64 decodedPoolId) = ConnectorMessages.parseAddPool(_message.ref(0)); + assertEq(decodedPoolId, uint256(poolId)); + } + + function testAllowPoolCurrencyEncoding() public { + assertEq( + ConnectorMessages.formatAllowPoolCurrency(99, 42), hex"0300000000000000630000000000000000000000000000002a" + ); + } + + function testAllowPoolCurrencyDecoding() public { + (uint64 actualPoolId, uint128 actualCurrency) = ConnectorMessages.parseAllowPoolCurrency( + fromHex("0300000000000000630000000000000000000000000000002a").ref(0) + ); + assertEq(actualPoolId, 99); + assertEq(uint256(actualCurrency), 42); + } + + function testAllowPoolCurrencyEquivalence(uint128 currency, uint64 poolId) public { + bytes memory _message = ConnectorMessages.formatAllowPoolCurrency(poolId, currency); + (uint64 decodedPoolId, uint128 decodedCurrency) = ConnectorMessages.parseAllowPoolCurrency(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); - assertEq(uint256(decodedCurrency), uint256(currency)); - assertEq(decodedDecimals, decimals); + assertEq(decodedCurrency, uint256(currency)); } function testAddTrancheEncoding() public { @@ -62,9 +86,10 @@ contract MessagesTest is Test { bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), "Some Name", "SYMBOL", + 18, 1000000000000000000000000000 ), - hex"020000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b536f6d65204e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053594d424f4c000000000000000000000000000000000000000000000000000000000000033b2e3c9fd0803ce8000000" + hex"040000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b536f6d65204e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053594d424f4c00000000000000000000000000000000000000000000000000001200000000033b2e3c9fd0803ce8000000" ); } @@ -74,16 +99,18 @@ contract MessagesTest is Test { bytes16 decodedTrancheId, string memory decodedTokenName, string memory decodedTokenSymbol, + uint8 decodedDecimals, uint128 decodedPrice ) = ConnectorMessages.parseAddTranche( fromHex( - "020000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b536f6d65204e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053594d424f4c000000000000000000000000000000000000000000000000000000000000033b2e3c9fd0803ce8000000" + "040000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b536f6d65204e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053594d424f4c00000000000000000000000000000000000000000000000000001200000000033b2e3c9fd0803ce8000000" ).ref(0) ); assertEq(uint256(decodedPoolId), uint256(12378532)); assertEq(decodedTrancheId, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b")); assertEq(decodedTokenName, bytes32ToString(bytes32("Some Name"))); assertEq(decodedTokenSymbol, bytes32ToString(bytes32("SYMBOL"))); + assertEq(decodedDecimals, 18); assertEq(decodedPrice, uint256(1000000000000000000000000000)); } @@ -92,14 +119,17 @@ contract MessagesTest is Test { bytes16 trancheId, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price ) public { - bytes memory _message = ConnectorMessages.formatAddTranche(poolId, trancheId, tokenName, tokenSymbol, price); + bytes memory _message = + ConnectorMessages.formatAddTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); ( uint64 decodedPoolId, bytes16 decodedTrancheId, string memory decodedTokenName, string memory decodedTokenSymbol, + uint8 decodedDecimals, uint128 decodedPrice ) = ConnectorMessages.parseAddTranche(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); @@ -110,6 +140,7 @@ contract MessagesTest is Test { // this intended behaviour. assertEq(decodedTokenName, bytes32ToString(stringToBytes32(tokenName))); assertEq(decodedTokenSymbol, bytes32ToString(stringToBytes32(tokenSymbol))); + assertEq(decodedDecimals, decimals); assertEq(uint256(decodedPrice), uint256(price)); } @@ -124,7 +155,7 @@ contract MessagesTest is Test { 0x1231231231231231231231231231231231231231, 1706260138 ), - hex"040000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000065b376aa" + hex"060000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000065b376aa" ); } @@ -158,7 +189,7 @@ contract MessagesTest is Test { ConnectorMessages.formatUpdateTrancheTokenPrice( 1, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), 1000000000000000000000000000 ), - fromHex("030000000000000001811acd5b3f17c06841c7e41e9e04cb1b00000000033b2e3c9fd0803ce8000000") + fromHex("050000000000000001811acd5b3f17c06841c7e41e9e04cb1b00000000033b2e3c9fd0803ce8000000") ); } @@ -189,20 +220,26 @@ contract MessagesTest is Test { 0x2222222222222222222222222222222222222222222222222222222222222222, 1000000000000000000000000000 ), - hex"050000000000000000000000000000002a1111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222200000000033b2e3c9fd0803ce8000000" + hex"070000000000000000000000000000002a1111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222200000000033b2e3c9fd0803ce8000000" ); } function testTransferDecoding() public { - (uint128 token, bytes32 sender, bytes32 receiver, uint128 amount) = ConnectorMessages.parseTransfer( - fromHex( - "050000000000000000000000000000002a1111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222200000000033b2e3c9fd0803ce8000000" - ).ref(0) - ); + bytes29 message = fromHex( + "070000000000000000000000000000002a1111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222200000000033b2e3c9fd0803ce8000000" + ).ref(0); + + (uint128 token, bytes32 sender, bytes32 recipient, uint128 amount) = ConnectorMessages.parseTransfer(message); assertEq(uint256(token), uint256(42)); assertEq(sender, 0x1111111111111111111111111111111111111111111111111111111111111111); - assertEq(receiver, 0x2222222222222222222222222222222222222222222222222222222222222222); + assertEq(recipient, 0x2222222222222222222222222222222222222222222222222222222222222222); assertEq(amount, uint256(1000000000000000000000000000)); + + // Test the optimised `parseIncomingTransfer` now + (uint128 token2, address recipient2, uint128 amount2) = ConnectorMessages.parseIncomingTransfer(message); + assertEq(uint256(token2), uint256(token)); + assertEq(recipient2, address(bytes20(recipient))); + assertEq(amount, amount2); } function testTransferEquivalence(uint128 token, bytes32 sender, bytes32 receiver, uint128 amount) public { @@ -213,6 +250,13 @@ contract MessagesTest is Test { assertEq(decodedSender, sender); assertEq(decodedReceiver, receiver); assertEq(decodedAmount, amount); + + // Test the optimised `parseIncomingTransfer` now + (uint128 decodedToken2, address decodedRecipient2, uint128 decodedAmount2) = + ConnectorMessages.parseIncomingTransfer(_message.ref(0)); + assertEq(uint256(decodedToken2), uint256(decodedToken)); + assertEq(decodedRecipient2, address(bytes20(decodedReceiver))); + assertEq(decodedAmount, decodedAmount2); } function testTransferTrancheTokensToEvmDomainEncoding() public { @@ -220,32 +264,24 @@ contract MessagesTest is Test { ConnectorMessages.formatTransferTrancheTokens( 1, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), + 0x1231231231231231231231231231231231231231231231231231231231231231, ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, 1284), - 1, 0x1231231231231231231231231231231231231231, 1000000000000000000000000000 ), - hex"060000000000000001811acd5b3f17c06841c7e41e9e04cb1b0100000000000005040000000000000000000000000000000000000000000000000000000000000001123123123123123123123123123123123123123100000000000000000000000000000000033b2e3c9fd0803ce8000000" + hex"080000000000000001811acd5b3f17c06841c7e41e9e04cb1b1231231231231231231231231231231231231231231231231231231231231231010000000000000504123123123123123123123123123123123123123100000000000000000000000000000000033b2e3c9fd0803ce8000000" ); } function testTransferTrancheTokensToEvmDomainDecoding() public { - ( - uint64 poolId, - bytes16 trancheId, - bytes9 domain, - uint256 destinationChainId, - address destinationAddress, - uint128 amount - ) = ConnectorMessages.parseTransferTrancheTokens20( + (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) = ConnectorMessages + .parseTransferTrancheTokens20( fromHex( - "060000000000000001811acd5b3f17c06841c7e41e9e04cb1b0100000000000005040000000000000000000000000000000000000000000000000000000000000001123123123123123123123123123123123123123100000000000000000000000000000000033b2e3c9fd0803ce8000000" + "080000000000000001811acd5b3f17c06841c7e41e9e04cb1b1231231231231231231231231231231231231231231231231231231231231231010000000000000504123123123123123123123123123123123123123100000000000000000000000000000000033b2e3c9fd0803ce8000000" ).ref(0) ); assertEq(uint256(poolId), uint256(1)); assertEq(trancheId, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b")); - assertEq(domain, ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, 1284)); - assertEq(destinationChainId, uint256(1)); assertEq(destinationAddress, 0x1231231231231231231231231231231231231231); assertEq(amount, uint256(1000000000000000000000000000)); } @@ -255,86 +291,79 @@ contract MessagesTest is Test { ConnectorMessages.formatTransferTrancheTokens( 1, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), + bytes32(bytes20(0x1231231231231231231231231231231231231231)), ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge), - 1, 0x1231231231231231231231231231231231231231231231231231231231231231, 1000000000000000000000000000 ), - hex"060000000000000001811acd5b3f17c06841c7e41e9e04cb1b0000000000000000000000000000000000000000000000000000000000000000000000000000000001123123123123123123123123123123123123123123123123123123123123123100000000033b2e3c9fd0803ce8000000" + hex"080000000000000001811acd5b3f17c06841c7e41e9e04cb1b1231231231231231231231231231231231231231000000000000000000000000000000000000000000123123123123123123123123123123123123123123123123123123123123123100000000033b2e3c9fd0803ce8000000" ); } function testTransferTrancheTokensToCentrifugeDecoding() public { - ( - uint64 poolId, - bytes16 trancheId, - bytes9 domain, - uint256 destinationChainId, - bytes32 destinationAddress, - uint128 amount - ) = ConnectorMessages.parseTransferTrancheTokens32( + (uint64 poolId, bytes16 trancheId, bytes32 sender, bytes9 domain, bytes32 destinationAddress, uint128 amount) = + ConnectorMessages.parseTransferTrancheTokens32( fromHex( - "060000000000000001811acd5b3f17c06841c7e41e9e04cb1b0000000000000000000000000000000000000000000000000000000000000000000000000000000001123123123123123123123123123123123123123123123123123123123123123100000000033b2e3c9fd0803ce8000000" + "080000000000000001811acd5b3f17c06841c7e41e9e04cb1b1231231231231231231231231231231231231231000000000000000000000000000000000000000000123123123123123123123123123123123123123123123123123123123123123100000000033b2e3c9fd0803ce8000000" ).ref(0) ); assertEq(uint256(poolId), uint256(1)); assertEq(trancheId, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b")); + assertEq(sender, 0x1231231231231231231231231231231231231231000000000000000000000000); assertEq(domain, ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge)); assertEq(destinationAddress, bytes32(hex"1231231231231231231231231231231231231231231231231231231231231231")); - assertEq(destinationChainId, uint256(1)); assertEq(amount, uint256(1000000000000000000000000000)); } function testTransferTrancheTokensToEvmEquivalence( uint64 poolId, bytes16 trancheId, - uint256 destinationChainId, + bytes32 sender, + uint64 destinationChainId, address destinationAddress, uint128 amount ) public { - bytes9 inputEncodedDomain = ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM); bytes memory _message = ConnectorMessages.formatTransferTrancheTokens( - poolId, trancheId, inputEncodedDomain, destinationChainId, destinationAddress, amount + poolId, + trancheId, + sender, + ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, destinationChainId), + destinationAddress, + amount ); - ( - uint64 decodedPoolId, - bytes16 decodedTrancheId, - bytes9 encodedDomain, - uint256 decodeddestinationChainId, - address decodedDestinationAddress, - uint256 decodedAmount - ) = ConnectorMessages.parseTransferTrancheTokens20(_message.ref(0)); + + (uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedDestinationAddress, uint256 decodedAmount) = + ConnectorMessages.parseTransferTrancheTokens20(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); - assertEq(encodedDomain, inputEncodedDomain); assertEq(decodedDestinationAddress, destinationAddress); - assertEq(decodeddestinationChainId, destinationChainId); assertEq(decodedAmount, amount); } function testTransferTrancheTokensToCentrifugeEquivalence( uint64 poolId, bytes16 trancheId, + address sender, bytes32 destinationAddress, uint128 amount ) public { bytes9 inputEncodedDomain = ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge); bytes memory _message = ConnectorMessages.formatTransferTrancheTokens( - poolId, trancheId, inputEncodedDomain, 0, destinationAddress, amount + poolId, trancheId, bytes32(bytes20(sender)), inputEncodedDomain, destinationAddress, amount ); ( uint64 decodedPoolId, bytes16 decodedTrancheId, + bytes32 decodedSender, bytes9 encodedDomain, - uint256 decodeddestinationChainId, bytes32 decodedDestinationAddress, uint256 decodedAmount ) = ConnectorMessages.parseTransferTrancheTokens32(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); + assertEq(decodedSender, bytes32(bytes20(sender))); assertEq(encodedDomain, inputEncodedDomain); assertEq(decodedDestinationAddress, destinationAddress); - assertEq(decodeddestinationChainId, 0); assertEq(decodedAmount, amount); } @@ -348,7 +377,7 @@ contract MessagesTest is Test { uint128(42), 1706260138 ), - hex"070000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" + hex"090000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" ); } @@ -356,7 +385,7 @@ contract MessagesTest is Test { (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 token, uint128 amount) = ConnectorMessages.parseIncreaseInvestOrder( fromHex( - "070000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" + "090000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" ).ref(0) ); assertEq(uint256(decodedPoolId), uint256(2)); @@ -399,7 +428,7 @@ contract MessagesTest is Test { uint128(42), 1706260138 ), - hex"080000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" + hex"0a0000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" ); } @@ -407,7 +436,7 @@ contract MessagesTest is Test { (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 token, uint128 amount) = ConnectorMessages.parseDecreaseInvestOrder( fromHex( - "080000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" + "0a0000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" ).ref(0) ); assertEq(uint256(decodedPoolId), uint256(2)); @@ -450,7 +479,7 @@ contract MessagesTest is Test { uint128(42), 1706260138 ), - hex"090000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" + hex"0b0000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" ); } @@ -458,7 +487,7 @@ contract MessagesTest is Test { (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 token, uint128 amount) = ConnectorMessages.parseIncreaseRedeemOrder( fromHex( - "090000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" + "0b0000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" ).ref(0) ); assertEq(uint256(decodedPoolId), uint256(2)); @@ -501,7 +530,7 @@ contract MessagesTest is Test { uint128(42), 1706260138 ), - hex"0a0000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" + hex"0c0000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" ); } @@ -509,7 +538,7 @@ contract MessagesTest is Test { (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 token, uint128 amount) = ConnectorMessages.parseDecreaseRedeemOrder( fromHex( - "0a0000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" + "0c0000000000000002811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312312312312312312312312312310000000000000000000000000000002a00000000000000000000000065b376aa" ).ref(0) ); assertEq(uint256(decodedPoolId), uint256(2)); @@ -542,76 +571,6 @@ contract MessagesTest is Test { assertEq(decodedAmount, amount); } - // CollectRedeem - function testCollectRedeemEncoding() public { - assertEq( - ConnectorMessages.formatCollectRedeem( - 2, - bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), - 0x1231231231231231231231231231231231231231231231231231231231231231 - ), - hex"0b0000000000000002811acd5b3f17c06841c7e41e9e04cb1b1231231231231231231231231231231231231231231231231231231231231231" - ); - } - - function testCollectRedeemDecoding() public { - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedUser) = ConnectorMessages.parseCollectRedeem( - fromHex( - "0b0000000000000002811acd5b3f17c06841c7e41e9e04cb1b1231231231231231231231231231231231231231231231231231231231231231" - ).ref(0) - ); - assertEq(uint256(decodedPoolId), uint256(2)); - assertEq(decodedTrancheId, hex"811acd5b3f17c06841c7e41e9e04cb1b"); - assertEq(decodedUser, 0x1231231231231231231231231231231231231231231231231231231231231231); - } - - function testCollectRedeemEquivalence(uint64 poolId, bytes16 trancheId, bytes32 user) public { - bytes memory _message = ConnectorMessages.formatCollectRedeem(poolId, trancheId, user); - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedUser) = - ConnectorMessages.parseCollectRedeem(_message.ref(0)); - - assertEq(uint256(decodedPoolId), uint256(poolId)); - assertEq(decodedTrancheId, trancheId); - assertEq(decodedUser, user); - } - - // CollectForRedeem - function testCollectForRedeemEncoding() public { - assertEq( - ConnectorMessages.formatCollectForRedeem( - 2, - bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), - 0x1111111111111111111111111111111111111111111111111111111111111111, - 0x2222222222222222222222222222222222222222222222222222222222222222 - ), - hex"0c0000000000000002811acd5b3f17c06841c7e41e9e04cb1b11111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222" - ); - } - - function testCollectForRedeemDecoding() public { - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedCaller, bytes32 decodedUser) = ConnectorMessages - .parseCollectForRedeem( - fromHex( - "0c0000000000000002811acd5b3f17c06841c7e41e9e04cb1b11111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222" - ).ref(0) - ); - assertEq(uint256(decodedPoolId), uint256(2)); - assertEq(decodedTrancheId, hex"811acd5b3f17c06841c7e41e9e04cb1b"); - assertEq(decodedCaller, 0x1111111111111111111111111111111111111111111111111111111111111111); - assertEq(decodedUser, 0x2222222222222222222222222222222222222222222222222222222222222222); - } - - function testCollectForRedeemEquivalence(uint64 poolId, bytes16 trancheId, bytes32 caller, bytes32 user) public { - bytes memory _message = ConnectorMessages.formatCollectForRedeem(poolId, trancheId, caller, user); - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedCaller, bytes32 decodedUser) = - ConnectorMessages.parseCollectForRedeem(_message.ref(0)); - - assertEq(uint256(decodedPoolId), uint256(poolId)); - assertEq(decodedTrancheId, trancheId); - assertEq(decodedCaller, caller); - assertEq(decodedUser, user); - } - // CollectInvest function testCollectInvestEncoding() public { assertEq( @@ -645,40 +604,36 @@ contract MessagesTest is Test { assertEq(decodedUser, user); } - // CollectForInvest - function testCollectForInvestEncoding() public { + // CollectRedeem + function testCollectRedeemEncoding() public { assertEq( - ConnectorMessages.formatCollectForInvest( + ConnectorMessages.formatCollectRedeem( 2, bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"), - 0x1111111111111111111111111111111111111111111111111111111111111111, - 0x2222222222222222222222222222222222222222222222222222222222222222 + 0x1231231231231231231231231231231231231231231231231231231231231231 ), - hex"0e0000000000000002811acd5b3f17c06841c7e41e9e04cb1b11111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222" + hex"0e0000000000000002811acd5b3f17c06841c7e41e9e04cb1b1231231231231231231231231231231231231231231231231231231231231231" ); } - function testCollectForInvestDecoding() public { - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedCaller, bytes32 decodedUser) = ConnectorMessages - .parseCollectForInvest( + function testCollectRedeemDecoding() public { + (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedUser) = ConnectorMessages.parseCollectRedeem( fromHex( - "0e0000000000000002811acd5b3f17c06841c7e41e9e04cb1b11111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222" + "0e0000000000000002811acd5b3f17c06841c7e41e9e04cb1b1231231231231231231231231231231231231231231231231231231231231231" ).ref(0) ); assertEq(uint256(decodedPoolId), uint256(2)); assertEq(decodedTrancheId, hex"811acd5b3f17c06841c7e41e9e04cb1b"); - assertEq(decodedCaller, 0x1111111111111111111111111111111111111111111111111111111111111111); - assertEq(decodedUser, 0x2222222222222222222222222222222222222222222222222222222222222222); + assertEq(decodedUser, 0x1231231231231231231231231231231231231231231231231231231231231231); } - function testCollectForInvestEquivalence(uint64 poolId, bytes16 trancheId, bytes32 caller, bytes32 user) public { - bytes memory _message = ConnectorMessages.formatCollectForInvest(poolId, trancheId, caller, user); - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedCaller, bytes32 decodedUser) = - ConnectorMessages.parseCollectForInvest(_message.ref(0)); + function testCollectRedeemEquivalence(uint64 poolId, bytes16 trancheId, bytes32 user) public { + bytes memory _message = ConnectorMessages.formatCollectRedeem(poolId, trancheId, user); + (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedUser) = + ConnectorMessages.parseCollectRedeem(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); - assertEq(decodedCaller, caller); assertEq(decodedUser, user); } diff --git a/test/accounts/PoolManager.sol b/test/accounts/PoolManager.sol index ef50b6c2..897597e6 100644 --- a/test/accounts/PoolManager.sol +++ b/test/accounts/PoolManager.sol @@ -16,17 +16,15 @@ contract InvariantPoolManager is Test { connector = connector_; } - function addPool(uint64 poolId, uint128 currency, uint8 decimals) public { - connector.addPool(poolId, currency, decimals); + function addPool(uint64 poolId) public { + connector.addPool(poolId); allPools.push(poolId); } - function addPoolAndTranche(uint64 poolId, uint128 currency, uint8 decimals, bytes16 trancheId, uint128 price) - public - { - addPool(poolId, currency, decimals); - connector.addTranche(poolId, trancheId, "-", "-", price); + function addPoolAndTranche(uint64 poolId, bytes16 trancheId, uint8 decimals, uint128 price) public { + addPool(poolId); + connector.addTranche(poolId, trancheId, "-", "-", decimals, price); allTranches.push(trancheId); trancheIdToPoolId[trancheId] = poolId; diff --git a/test/mock/MockHomeConnector.sol b/test/mock/MockHomeConnector.sol index 63bd20fc..b4d35807 100644 --- a/test/mock/MockHomeConnector.sol +++ b/test/mock/MockHomeConnector.sol @@ -34,8 +34,18 @@ contract MockHomeConnector is Test { router = XcmRouterLike(xcmRouter); } - function addPool(uint64 poolId, uint128 currency, uint8 decimals) public { - bytes memory _message = ConnectorMessages.formatAddPool(poolId, currency, decimals); + function addCurrency(uint128 currency, address currencyAddress) public { + bytes memory _message = ConnectorMessages.formatAddCurrency(currency, currencyAddress); + router.handle(_message); + } + + function addPool(uint64 poolId) public { + bytes memory _message = ConnectorMessages.formatAddPool(poolId); + router.handle(_message); + } + + function allowPoolCurrency(uint64 poolId, uint128 currency) public { + bytes memory _message = ConnectorMessages.formatAllowPoolCurrency(poolId, currency); router.handle(_message); } @@ -44,9 +54,11 @@ contract MockHomeConnector is Test { bytes16 trancheId, string memory tokenName, string memory tokenSymbol, + uint8 decimals, uint128 price ) public { - bytes memory _message = ConnectorMessages.formatAddTranche(poolId, trancheId, tokenName, tokenSymbol, price); + bytes memory _message = + ConnectorMessages.formatAddTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); router.handle(_message); } @@ -60,19 +72,25 @@ contract MockHomeConnector is Test { router.handle(_message); } - // Trigger an incoming (e.g. Centrifuge Chain -> EVM) transfer - function incomingTransfer( + // Trigger an incoming (e.g. Centrifuge Chain -> EVM) transfer of stable coins + function incomingTransfer(uint128 currency, bytes32 sender, bytes32 recipient, uint128 amount) public { + bytes memory _message = ConnectorMessages.formatTransfer(currency, sender, recipient, amount); + router.handle(_message); + } + + // Trigger an incoming (e.g. Centrifuge Chain -> EVM) transfer of tranche tokens + function incomingTransferTrancheTokens( uint64 poolId, bytes16 trancheId, - uint256 destinationChainId, + uint64 destinationChainId, address destinationAddress, uint128 amount ) public { bytes memory _message = ConnectorMessages.formatTransferTrancheTokens( poolId, trancheId, - ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM), - destinationChainId, + bytes32(bytes20(msg.sender)), + ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, destinationChainId), destinationAddress, amount );