diff --git a/.env.example b/.env.example index 80a258a0..7b5a8788 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ -RPC_URL= +CENTRIFUGE_CHAIN_ORIGIN=0x7369626cef070000000000000000000000000000 +MAINNET_RPC_URL=https://mainnet.infura.io/v3/... +POLYGON_RPC_URL=https://polygon-mainnet.infura.io/v3/... PRIVATE_KEY= ETHERSCAN_KEY= \ No newline at end of file diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index a3bfa541..61011443 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -34,7 +34,10 @@ jobs: id: test env: - FOUNDRY_PROFILE: pull_request + FOUNDRY_PROFILE: push_to_main + CENTRIFUGE_CHAIN_ORIGIN: ${{ secrets.CENTRIFUGE_CHAIN_ORIGIN }} + MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} + POLYGON_RPC_URL: ${{ secrets.POLYGON_RPC_URL }} lint: name: fmt diff --git a/.github/workflows/push-to-main.yml b/.github/workflows/push-to-main.yml index 0a97bedc..422119f0 100644 --- a/.github/workflows/push-to-main.yml +++ b/.github/workflows/push-to-main.yml @@ -36,4 +36,7 @@ jobs: forge test -vvv id: test env: - FOUNDRY_PROFILE: push_to_main \ No newline at end of file + FOUNDRY_PROFILE: push_to_main + CENTRIFUGE_CHAIN_ORIGIN: ${{ secrets.CENTRIFUGE_CHAIN_ORIGIN }} + MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} + POLYGON_RPC_URL: ${{ secrets.POLYGON_RPC_URL }} diff --git a/test/token/factory.sol b/test/token/factory.sol deleted file mode 100644 index 1803e433..00000000 --- a/test/token/factory.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.18; -pragma abicoder v2; - -import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol"; -import "forge-std/Test.sol"; - -contract FactoryTest is Test { - // address(0)[0:20] + keccak("Centrifuge")[21:32] - bytes32 SALT = 0x000000000000000000000000000000000000000075eb27011b69f002dc094d05; - - bool isFirstRun = false; - address tokenFactoryAddress; - address tokenAddress; - - function setUp() public {} - - // function testTokenAddressShouldBeDeterministic( - // address sender, - // uint64 chainId, - // string memory name, - // string memory symbol - // ) public { - // TrancheTokenFactory tokenFactory = new TrancheTokenFactory{ salt: SALT }(); - - // if (isFirstRun) { - // tokenFactoryAddress = address(tokenFactory); - // } else { - // assertEq(address(tokenFactory), tokenFactoryAddress); - // } - - // vm.prank(sender); - // vm.chainId(uint256(chainId)); - - // uint64 fixedPoolId = 1; - // bytes16 fixedTrancheId = "1"; - // uint8 fixedDecimals = 18; - - // address token = tokenFactory.newTrancheToken(fixedPoolId, fixedTrancheId, name, symbol, fixedDecimals); - - // if (isFirstRun) { - // tokenAddress = address(tokenFactory); - // isFirstRun = false; - // } else { - // assertEq(token, tokenAddress); - // } - // } -} diff --git a/test/token/factory.t.sol b/test/token/factory.t.sol new file mode 100644 index 00000000..e8bd8eac --- /dev/null +++ b/test/token/factory.t.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.18; +pragma abicoder v2; + +import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol"; +import {RestrictedToken} from "src/token/restricted.sol"; +import "forge-std/Test.sol"; + +contract FactoryTest is Test { + // address(0)[0:20] + keccak("Centrifuge")[21:32] + bytes32 SALT = 0x000000000000000000000000000000000000000075eb27011b69f002dc094d05; + uint256 mainnetFork; + uint256 polygonFork; + + function setUp() public { + mainnetFork = vm.createFork(vm.envString("MAINNET_RPC_URL")); + polygonFork = vm.createFork(vm.envString("POLYGON_RPC_URL")); + } + + function testTokenFactoryIsDeterministicAcrossChains( + address sender, + string memory name, + string memory symbol, + uint64 poolId, + bytes16 trancheId, + uint8 decimals + ) public { + vm.selectFork(mainnetFork); + TrancheTokenFactory tokenFactory1 = new TrancheTokenFactory{ salt: SALT }(); + address token1 = tokenFactory1.newTrancheToken(poolId, trancheId, name, symbol, decimals); + + vm.selectFork(polygonFork); + TrancheTokenFactory tokenFactory2 = new TrancheTokenFactory{ salt: SALT }(); + assertEq(address(tokenFactory1), address(tokenFactory2)); + vm.prank(sender); + address token2 = tokenFactory2.newTrancheToken(poolId, trancheId, name, symbol, decimals); + assertEq(address(token1), address(token2)); + } + + function testTokenFactoryShouldBeDeterministic() public { + address predictedAddress = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + SALT, + keccak256(abi.encodePacked(type(TrancheTokenFactory).creationCode)) + ) + ) + ) + ) + ); + TrancheTokenFactory tokenFactory = new TrancheTokenFactory{ salt: SALT }(); + assertEq(address(tokenFactory), predictedAddress); + } + + function testTrancheTokenShouldBeDeterministic( + uint64 poolId, + bytes16 trancheId, + string memory name, + string memory symbol, + uint8 decimals + ) public { + TrancheTokenFactory tokenFactory = new TrancheTokenFactory{ salt: SALT }(); + + bytes32 salt = keccak256(abi.encodePacked(poolId, trancheId)); + address predictedAddress = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + address(tokenFactory), + salt, + keccak256(abi.encodePacked(type(RestrictedToken).creationCode, abi.encode(decimals))) + ) + ) + ) + ) + ); + + address token = tokenFactory.newTrancheToken(poolId, trancheId, name, symbol, decimals); + + assertEq(address(token), predictedAddress); + } + + function testDeployingDeterministicAddressTwiceReverts( + uint64 poolId, + bytes16 trancheId, + string memory name, + string memory symbol, + uint8 decimals + ) public { + address predictedAddress = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + SALT, + keccak256(abi.encodePacked(type(TrancheTokenFactory).creationCode)) + ) + ) + ) + ) + ); + TrancheTokenFactory tokenFactory = new TrancheTokenFactory{ salt: SALT }(); + assertEq(address(tokenFactory), predictedAddress); + address token1 = tokenFactory.newTrancheToken(poolId, trancheId, name, symbol, decimals); + vm.expectRevert(); + address token2 = tokenFactory.newTrancheToken(poolId, trancheId, name, symbol, decimals); + } + + function testMemberlistFactoryIsDeterministicAcrossChains( + address sender, + uint64 poolId, + bytes16 trancheId, + uint256 threshold + ) public { + vm.selectFork(mainnetFork); + MemberlistFactory memberlistFactory1 = new MemberlistFactory{ salt: SALT }(); + address memberlist1 = memberlistFactory1.newMemberlist(); + + vm.selectFork(polygonFork); + MemberlistFactory memberlistFactory2 = new MemberlistFactory{ salt: SALT }(); + assertEq(address(memberlistFactory1), address(memberlistFactory2)); + vm.prank(sender); + address memberlist2 = memberlistFactory2.newMemberlist(); + assertEq(address(memberlist1), address(memberlist2)); + } + + function testMemberlistShouldBeDeterministic() public { + address predictedAddress = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + SALT, + keccak256(abi.encodePacked(type(MemberlistFactory).creationCode)) + ) + ) + ) + ) + ); + MemberlistFactory memberlistFactory = new MemberlistFactory{ salt: SALT }(); + assertEq(address(memberlistFactory), predictedAddress); + } +} diff --git a/test/token/restricted.sol b/test/token/restricted.t.sol similarity index 100% rename from test/token/restricted.sol rename to test/token/restricted.t.sol