From f229b7bbd2274097b99a3dca96072677d8135f81 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Thu, 28 Jul 2022 10:45:49 -0400 Subject: [PATCH 01/35] updating to new routing model --- src/TeleportRouter.sol | 53 ++++++++---- src/test/TeleportRouter.t.sol | 142 ++++++++++++++++++++++++++++----- src/test/mocks/GatewayMock.sol | 2 +- 3 files changed, 158 insertions(+), 39 deletions(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 2b49f09..df1e864 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -29,7 +29,7 @@ interface GatewayLike { uint256 maxFeePercentage, uint256 operatorFee ) external returns (uint256 postFeeAmount, uint256 totalFee); - function settle(bytes32 sourceDomain, uint256 batchedDaiToFlush) external; + function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) external; } contract TeleportRouter { @@ -38,15 +38,16 @@ contract TeleportRouter { mapping (address => uint256) public wards; // Auth mapping (bytes32 => address) public gateways; // GatewayLike contracts called by the router for each domain - mapping (address => bytes32) public domains; // Domains for each gateway EnumerableSet.Bytes32Set private allDomains; + address public parent; TokenLike immutable public dai; // L1 DAI ERC20 token event Rely(address indexed usr); event Deny(address indexed usr); event File(bytes32 indexed what, bytes32 indexed domain, address data); + event File(bytes32 indexed what, address data); modifier auth { require(wards[msg.sender] == 1, "TeleportRouter/not-authorized"); @@ -75,9 +76,8 @@ contract TeleportRouter { * and `requestMint()` calls to the gateway contract installed for a given domain. Gateway contracts must therefore * conform to the GatewayLike interface. Examples of valid gateways include TeleportJoin (for the L1 domain) * and L1 bridge contracts (for L2 domains). - * @dev In addition to updating the mapping `gateways` which maps GatewayLike contracts to domain names and - * the reverse mapping `domains` which maps domain names to GatewayLike contracts, this method also maintains - * the enumerable set `allDomains`. + * @dev In addition to updating the mapping `gateways` which maps GatewayLike contracts to domain names this method + * also maintains the enumerable set `allDomains`. * @param what The name of the operation. Only "gateway" is supported. * @param domain The domain for which a GatewayLike contract is added, replaced or removed. * @param data The address of the GatewayLike contract to install for the domain (or address(0) to remove a domain) @@ -91,8 +91,7 @@ contract TeleportRouter { allDomains.add(domain); } } else { - // existing domain - domains[prevGateway] = bytes32(0); + // existing domain if(data == address(0)) { // => remove domain from allDomains allDomains.remove(domain); @@ -100,15 +99,27 @@ contract TeleportRouter { } gateways[domain] = data; - if(data != address(0)) { - domains[data] = domain; - } } else { revert("TeleportRouter/file-unrecognized-param"); } emit File(what, domain, data); } + /** + * @notice Allows auth to configure the router. The only supported operation is "parent", + * which sets the fallback address if no specific domain is matched. + * @param what The name of the operation. Only "parent" is supported. + * @param data Set the fallback gateway or address(0) to disable the fallback. + */ + function file(bytes32 what, address data) external auth { + if (what == "parent") { + parent = data; + } else { + revert("TeleportRouter/file-unrecognized-param"); + } + emit File(what, data); + } + function numDomains() external view returns (uint256) { return allDomains.length(); } @@ -132,8 +143,12 @@ contract TeleportRouter { uint256 maxFeePercentage, uint256 operatorFee ) external returns (uint256 postFeeAmount, uint256 totalFee) { - require(msg.sender == gateways[teleportGUID.sourceDomain], "TeleportRouter/sender-not-gateway"); + // We trust the parent gateway with any sourceDomain as a compromised parent implies compromised child + // Otherwise we restrict passing messages only from the actual source domain + require(msg.sender == parent || msg.sender == gateways[teleportGUID.sourceDomain], "TeleportRouter/sender-not-gateway"); address gateway = gateways[teleportGUID.targetDomain]; + // Use fallback if no gateway is configured for the target domain + if (gateway == address(0)) gateway = parent; require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); (postFeeAmount, totalFee) = GatewayLike(gateway).requestMint(teleportGUID, maxFeePercentage, operatorFee); } @@ -141,16 +156,20 @@ contract TeleportRouter { /** * @notice Call a GatewayLike contract to settle a batch of sourceDomain -> targetDomain DAI transfer. * The sender must be a supported gateway - * @param targetDomain The domain receiving the batch of DAI (only L1 supported for now) + * @param sourceDomain The domain sending the batch of DAI + * @param targetDomain The domain receiving the batch of DAI * @param batchedDaiToFlush The amount of DAI in the batch */ - function settle(bytes32 targetDomain, uint256 batchedDaiToFlush) external { - bytes32 sourceDomain = domains[msg.sender]; - require(sourceDomain != bytes32(0), "TeleportRouter/sender-not-gateway"); + function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) external { + // We trust the parent gateway with any sourceDomain as a compromised parent implies compromised child + // Otherwise we restrict passing messages only from the actual source domain + require(msg.sender == parent || msg.sender == gateways[sourceDomain], "TeleportRouter/sender-not-gateway"); address gateway = gateways[targetDomain]; + // Use fallback if no gateway is configured for the target domain + if (gateway == address(0)) gateway = parent; require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); - // Forward the DAI to settle to the gateway contract + // Forward the DAI to settle to the gateway contract dai.transferFrom(msg.sender, gateway, batchedDaiToFlush); - GatewayLike(gateway).settle(sourceDomain, batchedDaiToFlush); + GatewayLike(gateway).settle(sourceDomain, targetDomain, batchedDaiToFlush); } } diff --git a/src/test/TeleportRouter.t.sol b/src/test/TeleportRouter.t.sol index 7816d04..6b3d368 100644 --- a/src/test/TeleportRouter.t.sol +++ b/src/test/TeleportRouter.t.sol @@ -50,6 +50,10 @@ contract TeleportRouterTest is DSTest { (ok,) = address(router).call(abi.encodeWithSignature("file(bytes32,bytes32,address)", what, domain, data)); } + function _tryFile(bytes32 what, address data) internal returns (bool ok) { + (ok,) = address(router).call(abi.encodeWithSignature("file(bytes32,address)", what, data)); + } + function testConstructor() public { assertEq(address(router.dai()), dai); assertEq(router.wards(address(this)), 1); @@ -70,33 +74,31 @@ contract TeleportRouterTest is DSTest { function testFileFailsWhenNotAuthed() public { assertTrue(_tryFile("gateway", "dom", address(888))); + assertTrue(_tryFile("parent", address(888))); router.deny(address(this)); assertTrue(!_tryFile("gateway", "dom", address(888))); + assertTrue(!_tryFile("parent", address(888))); } function testFileNewDomains() public { bytes32 domain1 = "newdom1"; address gateway1 = address(111); assertEq(router.gateways(domain1), address(0)); - assertEq(router.domains(gateway1), bytes32(0)); assertEq(router.numDomains(), 0); assertTrue(_tryFile("gateway", domain1, gateway1)); assertEq(router.gateways(domain1), gateway1); - assertEq(router.domains(gateway1), domain1); assertEq(router.numDomains(), 1); assertEq(router.domainAt(0), domain1); bytes32 domain2 = "newdom2"; address gateway2 = address(222); assertEq(router.gateways(domain2), address(0)); - assertEq(router.domains(gateway2), bytes32(0)); assertTrue(_tryFile("gateway", domain2, gateway2)); assertEq(router.gateways(domain2), gateway2); - assertEq(router.domains(gateway2), domain2); assertEq(router.numDomains(), 2); assertEq(router.domainAt(0), domain1); assertEq(router.domainAt(1), domain2); @@ -107,7 +109,6 @@ contract TeleportRouterTest is DSTest { address gateway1 = address(111); assertTrue(_tryFile("gateway", domain, gateway1)); assertEq(router.gateways(domain), gateway1); - assertEq(router.domains(gateway1), domain); assertEq(router.numDomains(), 1); assertEq(router.domainAt(0), domain); address gateway2 = address(222); @@ -115,8 +116,6 @@ contract TeleportRouterTest is DSTest { assertTrue(_tryFile("gateway", domain, gateway2)); assertEq(router.gateways(domain), gateway2); - assertEq(router.domains(gateway1), bytes32(0)); - assertEq(router.domains(gateway2), domain); assertEq(router.numDomains(), 1); assertEq(router.domainAt(0), domain); } @@ -126,7 +125,6 @@ contract TeleportRouterTest is DSTest { address gateway = address(111); assertTrue(_tryFile("gateway", domain, gateway)); assertEq(router.gateways(domain), gateway); - assertEq(router.domains(gateway), domain); assertEq(router.numDomains(), 1); assertEq(router.domainAt(0), domain); @@ -134,7 +132,6 @@ contract TeleportRouterTest is DSTest { assertTrue(_tryFile("gateway", domain, address(0))); assertEq(router.gateways(domain), address(0)); - assertEq(router.domains(gateway), bytes32(0)); assertTrue(!router.hasDomain(domain)); } @@ -148,8 +145,6 @@ contract TeleportRouterTest is DSTest { assertTrue(_tryFile("gateway", domain2, gateway2)); assertEq(router.gateways(domain1), gateway1); assertEq(router.gateways(domain2), gateway2); - assertEq(router.domains(gateway1), domain1); - assertEq(router.domains(gateway2), domain2); assertEq(router.numDomains(), 2); assertEq(router.domainAt(0), domain1); assertEq(router.domainAt(1), domain2); @@ -159,8 +154,6 @@ contract TeleportRouterTest is DSTest { assertEq(router.gateways(domain1), address(0)); assertEq(router.gateways(domain2), gateway2); - assertEq(router.domains(gateway1), bytes32(0)); - assertEq(router.domains(gateway2), domain2); assertEq(router.numDomains(), 1); assertEq(router.domainAt(0), domain2); @@ -169,15 +162,97 @@ contract TeleportRouterTest is DSTest { assertEq(router.gateways(domain1), gateway1); assertEq(router.gateways(domain2), gateway2); - assertEq(router.domains(gateway1), domain1); - assertEq(router.domains(gateway2), domain2); assertEq(router.numDomains(), 2); assertEq(router.domainAt(0), domain2); // domains have been swapped compared to initial state assertEq(router.domainAt(1), domain1); } - function testFailFileInvalidWhat() public { - router.file("meh", "aaa", address(888)); + function testFileTwoDomainsSameGateway() public { + bytes32 domain1 = "dom1"; + bytes32 domain2 = "dom2"; + address gateway1 = address(111); + assertTrue(_tryFile("gateway", domain1, gateway1)); + assertTrue(_tryFile("gateway", domain2, gateway1)); + assertEq(router.gateways(domain1), gateway1); + assertEq(router.gateways(domain2), gateway1); + assertEq(router.numDomains(), 2); + assertEq(router.domainAt(0), domain1); + assertEq(router.domainAt(1), domain2); + } + + function testFileTwoDomainsSameGatewayRemove1() public { + bytes32 domain1 = "dom1"; + bytes32 domain2 = "dom2"; + address gateway1 = address(111); + assertTrue(_tryFile("gateway", domain1, gateway1)); + assertTrue(_tryFile("gateway", domain2, gateway1)); + + assertTrue(_tryFile("gateway", domain2, address(0))); + + assertEq(router.gateways(domain1), gateway1); + assertEq(router.gateways(domain2), address(0)); + assertEq(router.numDomains(), 1); + assertEq(router.domainAt(0), domain1); + } + + function testFileTwoDomainsSameGatewayRemove2() public { + bytes32 domain1 = "dom1"; + bytes32 domain2 = "dom2"; + address gateway1 = address(111); + assertTrue(_tryFile("gateway", domain1, gateway1)); + assertTrue(_tryFile("gateway", domain2, gateway1)); + + assertTrue(_tryFile("gateway", domain1, address(0))); + assertTrue(_tryFile("gateway", domain2, address(0))); + + assertEq(router.gateways(domain1), address(0)); + assertEq(router.gateways(domain2), address(0)); + assertEq(router.numDomains(), 0); + } + + function testFileTwoDomainsSameGatewaySplit() public { + bytes32 domain1 = "dom1"; + bytes32 domain2 = "dom2"; + address gateway1 = address(111); + address gateway2 = address(222); + assertTrue(_tryFile("gateway", domain1, gateway1)); + assertTrue(_tryFile("gateway", domain2, gateway1)); + + assertTrue(_tryFile("gateway", domain2, gateway2)); + + assertEq(router.gateways(domain1), gateway1); + assertEq(router.gateways(domain2), gateway2); + assertEq(router.numDomains(), 2); + assertEq(router.domainAt(0), domain1); + assertEq(router.domainAt(1), domain2); + } + + function testFileTwoDomainsSameGatewaySplitRemove() public { + bytes32 domain1 = "dom1"; + bytes32 domain2 = "dom2"; + address gateway1 = address(111); + address gateway2 = address(222); + assertTrue(_tryFile("gateway", domain1, gateway1)); + assertTrue(_tryFile("gateway", domain2, gateway1)); + + assertTrue(_tryFile("gateway", domain2, gateway2)); + assertTrue(_tryFile("gateway", domain1, address(0))); + + assertEq(router.gateways(domain1), address(0)); + assertEq(router.gateways(domain2), gateway2); + assertEq(router.numDomains(), 1); + assertEq(router.domainAt(0), domain2); + } + + function testFileParent() public { + assertTrue(_tryFile("parent", address(123))); + + assertEq(router.parent(), address(123)); + } + + function testFileInvalidWhat() public { + assertTrue(!_tryFile("meh", "aaa", address(888))); + assertTrue(!_tryFile("meh", address(888))); } function testFailRequestMintFromNotGateway() public { @@ -242,12 +317,28 @@ contract TeleportRouterTest is DSTest { router.requestMint(guid, 4 * WAD / 10000, 0); } + function testRequestMintFromParent() public { + TeleportGUID memory guid = TeleportGUID({ + sourceDomain: "l2network", + targetDomain: "another-l2network", + receiver: addressToBytes32(address(123)), + operator: addressToBytes32(address(234)), + amount: 250_000 ether, + nonce: 5, + timestamp: uint48(block.timestamp) + }); + router.file("parent", address(this)); + router.file("gateway", "another-l2network", address(new GatewayMock())); + + router.requestMint(guid, 4 * WAD / 10000, 0); + } + function testFailSettleFromNotGateway() public { router.file("gateway", "l2network", address(555)); DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); - router.settle(l1Domain, 100 ether); + router.settle("l2network", l1Domain, 100 ether); } function testSettleTargetingL1() public { @@ -256,7 +347,7 @@ contract TeleportRouterTest is DSTest { DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); - router.settle(l1Domain, 100 ether); + router.settle("l2network", l1Domain, 100 ether); } function testSettleTargetingL2() public { @@ -265,12 +356,21 @@ contract TeleportRouterTest is DSTest { DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); - router.settle("another-l2network", 100 ether); + router.settle("l2network", "another-l2network", 100 ether); + } + + function testSettleFromParent() public { + router.file("parent", address(this)); + router.file("gateway", "another-l2network", address(new GatewayMock())); + DaiMock(dai).mint(address(this), 100 ether); + DaiMock(dai).approve(address(router), 100 ether); + + router.settle("l2network", "another-l2network", 100 ether); } function testFailSettleTargetingInvalidDomain() public { router.file("gateway", "l2network", address(this)); - router.settle("invalid-network", 100 ether); + router.settle("l2network", "invalid-network", 100 ether); } } diff --git a/src/test/mocks/GatewayMock.sol b/src/test/mocks/GatewayMock.sol index 477b411..b7b77ed 100644 --- a/src/test/mocks/GatewayMock.sol +++ b/src/test/mocks/GatewayMock.sol @@ -5,5 +5,5 @@ import "src/TeleportGUID.sol"; contract GatewayMock { function requestMint(TeleportGUID calldata, uint256, uint256) external pure returns (uint256 postFeeAmount, uint256 totalFee) {} - function settle(bytes32 sourceDomain, uint256 batchedDaiToFlush) external {} + function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) external {} } From 6a104eb1d48a11aed0545d9781f044fbf17f65b1 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Thu, 28 Jul 2022 11:07:47 -0400 Subject: [PATCH 02/35] update join to use new settle --- src/TeleportJoin.sol | 4 +++- src/test/TeleportJoin.t.sol | 14 +++++++------- src/test/integration/TeleportJoin.t.sol | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/TeleportJoin.sol b/src/TeleportJoin.sol index f607c02..29acc4a 100644 --- a/src/TeleportJoin.sol +++ b/src/TeleportJoin.sol @@ -253,9 +253,11 @@ contract TeleportJoin { /** * @dev External function that repays debt with DAI previously pushed to this contract (in general coming from the bridges) * @param sourceDomain domain where the DAI is coming from + * @param targetDomain this domain * @param batchedDaiToFlush Amount of DAI that is being processed for repayment **/ - function settle(bytes32 sourceDomain, uint256 batchedDaiToFlush) external { + function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) external { + require(targetDomain == domain, "TeleportJoin/incorrect-targetDomain"); require(batchedDaiToFlush <= 2 ** 255, "TeleportJoin/overflow"); daiJoin.join(address(this), batchedDaiToFlush); if (vat.live() == 1) { diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index 87cd2ac..5027f12 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -493,7 +493,7 @@ contract TeleportJoinTest is DSTest { vat.suck(address(0), address(this), 100_000 * RAD); daiJoin.exit(address(join), 100_000 ether); - join.settle("l2network", 100_000 ether); + join.settle("l2network", domain, 100_000 ether); assertEq(join.debt("l2network"), -100_000 ether); assertEq(join.cure(), 0); @@ -503,7 +503,7 @@ contract TeleportJoinTest is DSTest { vat.suck(address(0), address(this), 100_000 * RAD); daiJoin.exit(address(join), 100_000 ether); - join.settle("l2network", 100_000 ether); + join.settle("l2network", domain, 100_000 ether); assertEq(join.debt("l2network"), -100_000 ether); assertEq(join.cure(), 0); @@ -530,7 +530,7 @@ contract TeleportJoinTest is DSTest { vat.suck(address(0), address(this), 100_000 * RAD); daiJoin.exit(address(join), 100_000 ether); - join.settle("l2network", 100_000 ether); + join.settle("l2network", domain, 100_000 ether); assertEq(join.debt("l2network"), -100_000 ether); assertEq(join.cure(), 0); @@ -559,7 +559,7 @@ contract TeleportJoinTest is DSTest { vat.suck(address(0), address(this), 100_000 * RAD); daiJoin.exit(address(join), 100_000 ether); - join.settle("l2network", 100_000 ether); + join.settle("l2network", domain, 100_000 ether); assertEq(join.debt("l2network"), -100_000 ether); assertEq(join.cure(), 0); @@ -611,7 +611,7 @@ contract TeleportJoinTest is DSTest { vat.suck(address(0), address(this), 250_000 * RAD); daiJoin.exit(address(join), 250_000 ether); - join.settle("l2network", 250_000 ether); + join.settle("l2network", domain, 250_000 ether); assertEq(join.debt("l2network"), 0); assertEq(_ink(), 250_000 ether); @@ -728,7 +728,7 @@ contract TeleportJoinTest is DSTest { vat.suck(address(0), address(this), 100_000 * RAD); daiJoin.exit(address(join), 100_000 ether); - join.settle("l2network", 100_000 ether); + join.settle("l2network", domain, 100_000 ether); TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network_2", @@ -775,7 +775,7 @@ contract TeleportJoinTest is DSTest { vat.suck(address(0), address(this), 10_000 * RAD); daiJoin.exit(address(join), 10_000 ether); - join.settle("l2network_3", 10_000 ether); + join.settle("l2network_3", domain, 10_000 ether); assertEq(join.debt("l2network"), -50_000 ether); assertEq(join.debt("l2network_2"), 150_000 ether); diff --git a/src/test/integration/TeleportJoin.t.sol b/src/test/integration/TeleportJoin.t.sol index 853a443..e153851 100644 --- a/src/test/integration/TeleportJoin.t.sol +++ b/src/test/integration/TeleportJoin.t.sol @@ -142,7 +142,7 @@ contract TeleportJoinIntegrationTest is DSTest { assertEq(vat.dai(address(teleportJoin)), 0); dai.transfer(address(teleportJoin), teleportAmount); - teleportJoin.settle(SLAVE_DOMAIN, teleportAmount); + teleportJoin.settle(SLAVE_DOMAIN, MASTER_DOMAIN, teleportAmount); assertEq(vat.dai(address(teleportJoin)), teleportAmount * RAY); // the dai is now locked in teleportJoin assertEq(teleportJoin.cure(), teleportAmount * RAY); // the debt was not actually settled From 09310e1ee2479a8743458d436e50bad0394071ae Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Thu, 28 Jul 2022 11:39:23 -0400 Subject: [PATCH 03/35] add registerMint() to register without minting --- src/TeleportJoin.sol | 14 ++++++++++++++ src/TeleportRouter.sol | 22 +++++----------------- src/test/TeleportRouter.t.sol | 20 ++++++++++---------- src/test/mocks/GatewayMock.sol | 3 ++- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/TeleportJoin.sol b/src/TeleportJoin.sol index 29acc4a..c8b31dc 100644 --- a/src/TeleportJoin.sol +++ b/src/TeleportJoin.sol @@ -211,6 +211,20 @@ contract TeleportJoin { emit Mint(hashGUID, teleportGUID, amtToTake, maxFeePercentage, operatorFee, msg.sender); } + /** + * @dev External authed function that registers the teleport + * @param teleportGUID Struct which contains the whole teleport data + **/ + function registerMint( + TeleportGUID calldata teleportGUID + ) external auth { + bytes32 hashGUID = getGUIDHash(teleportGUID); + require(!teleports[hashGUID].blessed, "TeleportJoin/already-blessed"); + teleports[hashGUID].blessed = true; + teleports[hashGUID].pending = teleportGUID.amount; + emit Register(hashGUID, teleportGUID); + } + /** * @dev External authed function that registers the teleport and executes the mint after * @param teleportGUID Struct which contains the whole teleport data diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index df1e864..2bb8656 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -24,11 +24,7 @@ interface TokenLike { } interface GatewayLike { - function requestMint( - TeleportGUID calldata teleportGUID, - uint256 maxFeePercentage, - uint256 operatorFee - ) external returns (uint256 postFeeAmount, uint256 totalFee); + function registerMint(TeleportGUID calldata teleportGUID) external; function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) external; } @@ -73,7 +69,7 @@ contract TeleportRouter { /** * @notice Allows auth to configure the router. The only supported operation is "gateway", * which allows adding, replacing or removing a gateway contract for a given domain. The router forwards `settle()` - * and `requestMint()` calls to the gateway contract installed for a given domain. Gateway contracts must therefore + * and `registerMint()` calls to the gateway contract installed for a given domain. Gateway contracts must therefore * conform to the GatewayLike interface. Examples of valid gateways include TeleportJoin (for the L1 domain) * and L1 bridge contracts (for L2 domains). * @dev In addition to updating the mapping `gateways` which maps GatewayLike contracts to domain names this method @@ -131,18 +127,10 @@ contract TeleportRouter { } /** - * @notice Call a GatewayLike contract to request the minting of DAI. The sender must be a supported gateway + * @notice Call a GatewayLike contract to register the minting of DAI. The sender must be a supported gateway * @param teleportGUID The teleport GUID to register - * @param maxFeePercentage Max percentage of the withdrawn amount (in WAD) to be paid as fee (e.g 1% = 0.01 * WAD) - * @param operatorFee The amount of DAI to pay to the operator - * @return postFeeAmount The amount of DAI sent to the receiver after taking out fees - * @return totalFee The total amount of DAI charged as fees */ - function requestMint( - TeleportGUID calldata teleportGUID, - uint256 maxFeePercentage, - uint256 operatorFee - ) external returns (uint256 postFeeAmount, uint256 totalFee) { + function registerMint(TeleportGUID calldata teleportGUID) external { // We trust the parent gateway with any sourceDomain as a compromised parent implies compromised child // Otherwise we restrict passing messages only from the actual source domain require(msg.sender == parent || msg.sender == gateways[teleportGUID.sourceDomain], "TeleportRouter/sender-not-gateway"); @@ -150,7 +138,7 @@ contract TeleportRouter { // Use fallback if no gateway is configured for the target domain if (gateway == address(0)) gateway = parent; require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); - (postFeeAmount, totalFee) = GatewayLike(gateway).requestMint(teleportGUID, maxFeePercentage, operatorFee); + GatewayLike(gateway).registerMint(teleportGUID); } /** diff --git a/src/test/TeleportRouter.t.sol b/src/test/TeleportRouter.t.sol index 6b3d368..7e69dad 100644 --- a/src/test/TeleportRouter.t.sol +++ b/src/test/TeleportRouter.t.sol @@ -255,7 +255,7 @@ contract TeleportRouterTest is DSTest { assertTrue(!_tryFile("meh", address(888))); } - function testFailRequestMintFromNotGateway() public { + function testFailRegisterMintFromNotGateway() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", targetDomain: l1Domain, @@ -267,10 +267,10 @@ contract TeleportRouterTest is DSTest { }); router.file("gateway", "l2network", address(555)); - router.requestMint(guid, 4 * WAD / 10000, 0); + router.registerMint(guid); } - function testRequestMintTargetingL1() public { + function testRegisterMintTargetingL1() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", targetDomain: l1Domain, @@ -283,10 +283,10 @@ contract TeleportRouterTest is DSTest { router.file("gateway", "l2network", address(this)); router.file("gateway", l1Domain, teleportJoin); - router.requestMint(guid, 4 * WAD / 10000, 0); + router.registerMint(guid); } - function testRequestMintTargetingL2() public { + function testRegisterMintTargetingL2() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", targetDomain: "another-l2network", @@ -299,10 +299,10 @@ contract TeleportRouterTest is DSTest { router.file("gateway", "l2network", address(this)); router.file("gateway", "another-l2network", address(new GatewayMock())); - router.requestMint(guid, 4 * WAD / 10000, 0); + router.registerMint(guid); } - function testFailRequestMintTargetingInvalidDomain() public { + function testFailRegisterMintTargetingInvalidDomain() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", targetDomain: "invalid-network", @@ -314,10 +314,10 @@ contract TeleportRouterTest is DSTest { }); router.file("gateway", "l2network", address(this)); - router.requestMint(guid, 4 * WAD / 10000, 0); + router.registerMint(guid); } - function testRequestMintFromParent() public { + function testRegisterMintFromParent() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", targetDomain: "another-l2network", @@ -330,7 +330,7 @@ contract TeleportRouterTest is DSTest { router.file("parent", address(this)); router.file("gateway", "another-l2network", address(new GatewayMock())); - router.requestMint(guid, 4 * WAD / 10000, 0); + router.registerMint(guid); } function testFailSettleFromNotGateway() public { diff --git a/src/test/mocks/GatewayMock.sol b/src/test/mocks/GatewayMock.sol index b7b77ed..c85ddde 100644 --- a/src/test/mocks/GatewayMock.sol +++ b/src/test/mocks/GatewayMock.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.14; import "src/TeleportGUID.sol"; contract GatewayMock { + function registerMint(TeleportGUID calldata) external {} function requestMint(TeleportGUID calldata, uint256, uint256) external pure returns (uint256 postFeeAmount, uint256 totalFee) {} - function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) external {} + function settle(bytes32, bytes32, uint256) external {} } From 1c5fb6fbaac9a2b175d96982e07586116f350aba Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Thu, 28 Jul 2022 12:23:29 -0400 Subject: [PATCH 04/35] add initiateTeleport and flush to TeleportJoin; restrict settle to just the router --- src/TeleportJoin.sol | 81 ++++++++++++++++++++----- src/test/TeleportJoin.t.sol | 3 +- src/test/integration/TeleportJoin.t.sol | 2 +- 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/src/TeleportJoin.sol b/src/TeleportJoin.sol index c8b31dc..5c83b3c 100644 --- a/src/TeleportJoin.sol +++ b/src/TeleportJoin.sol @@ -36,6 +36,7 @@ interface DaiJoinLike { } interface TokenLike { + function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); function approve(address, uint256) external returns (bool); } @@ -43,22 +44,31 @@ interface FeesLike { function getFee(TeleportGUID calldata, uint256, int256, uint256, uint256) external view returns (uint256); } +interface GatewayLike { + function registerMint(TeleportGUID calldata teleportGUID) external; + function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) external; +} + // Primary control for extending Teleport credit contract TeleportJoin { - mapping (address => uint256) public wards; // Auth - mapping (bytes32 => address) public fees; // Fees contract per source domain - mapping (bytes32 => uint256) public line; // Debt ceiling per source domain - mapping (bytes32 => int256) public debt; // Outstanding debt per source domain (can be < 0 when settlement occurs before mint) - mapping (bytes32 => TeleportStatus) public teleports; // Approved teleports and pending unpaid + mapping (address => uint256) public wards; // Auth + mapping (bytes32 => address) public fees; // Fees contract per source domain + mapping (bytes32 => uint256) public line; // Debt ceiling per source domain + mapping (bytes32 => int256) public debt; // Outstanding debt per source domain (can be < 0 when settlement occurs before mint) + mapping (bytes32 => TeleportStatus) public teleports; // Approved teleports and pending unpaid + mapping (bytes32 => uint256) public batchedDaiToFlush; // Pending DAI to flush per target domain address public vow; uint256 internal art; // We need to preserve the last art value before the position being skimmed (End) + uint80 public nonce; VatLike immutable public vat; DaiJoinLike immutable public daiJoin; + TokenLike immutable public dai; bytes32 immutable public ilk; bytes32 immutable public domain; + GatewayLike immutable public router; uint256 constant public WAD = 10 ** 18; uint256 constant public RAY = 10 ** 27; @@ -73,21 +83,26 @@ contract TeleportJoin { bytes32 indexed hashGUID, TeleportGUID teleportGUID, uint256 amount, uint256 maxFeePercentage, uint256 operatorFee, address originator ); event Settle(bytes32 indexed sourceDomain, uint256 batchedDaiToFlush); + event InitiateTeleport(TeleportGUID teleport); + event Flush(bytes32 indexed targetDomain, uint256 dai); struct TeleportStatus { bool blessed; uint248 pending; } - constructor(address vat_, address daiJoin_, bytes32 ilk_, bytes32 domain_) { + constructor(address vat_, address daiJoin_, bytes32 ilk_, bytes32 domain_, address router_) { wards[msg.sender] = 1; emit Rely(msg.sender); vat = VatLike(vat_); daiJoin = DaiJoinLike(daiJoin_); + dai = daiJoin.dai(); vat.hope(daiJoin_); - daiJoin.dai().approve(daiJoin_, type(uint256).max); + dai.approve(daiJoin_, type(uint256).max); ilk = ilk_; domain = domain_; + router = GatewayLike(router_); + dai.approve(router_, type(uint256).max); } function _min(uint256 x, uint256 y) internal pure returns (uint256 z) { @@ -268,22 +283,60 @@ contract TeleportJoin { * @dev External function that repays debt with DAI previously pushed to this contract (in general coming from the bridges) * @param sourceDomain domain where the DAI is coming from * @param targetDomain this domain - * @param batchedDaiToFlush Amount of DAI that is being processed for repayment + * @param amount Amount of DAI that is being processed for repayment **/ - function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) external { + function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) external { + require(msg.sender == address(router), "TeleportJoin/not-router"); require(targetDomain == domain, "TeleportJoin/incorrect-targetDomain"); - require(batchedDaiToFlush <= 2 ** 255, "TeleportJoin/overflow"); - daiJoin.join(address(this), batchedDaiToFlush); + require(amount <= 2 ** 255, "TeleportJoin/overflow"); + daiJoin.join(address(this), amount); if (vat.live() == 1) { (, uint256 art_) = vat.urns(ilk, address(this)); // rate == RAY => normalized debt == actual debt - uint256 amtToPayBack = _min(batchedDaiToFlush, art_); + uint256 amtToPayBack = _min(amount, art_); vat.frob(ilk, address(this), address(this), address(this), -int256(amtToPayBack), -int256(amtToPayBack)); vat.slip(ilk, address(this), -int256(amtToPayBack)); unchecked { art = art_ - amtToPayBack; // Always safe operation } } - debt[sourceDomain] -= int256(batchedDaiToFlush); - emit Settle(sourceDomain, batchedDaiToFlush); + debt[sourceDomain] -= int256(amount); + emit Settle(sourceDomain, amount); + } + + function initiateTeleport( + bytes32 targetDomain, + bytes32 receiver, + uint128 amount, + bytes32 operator + ) external { + TeleportGUID memory teleport = TeleportGUID({ + sourceDomain: domain, + targetDomain: targetDomain, + receiver: receiver, + operator: operator, + amount: amount, + nonce: nonce++, + timestamp: uint48(block.timestamp) + }); + + batchedDaiToFlush[targetDomain] += amount; + require(dai.transferFrom(msg.sender, address(this), amount), "DomainHost/transfer-failed"); + + // Initiate the censorship-resistant slow-path + router.registerMint(teleport); + + // Oracle listens to this event for the fast-path + emit InitiateTeleport(teleport); + } + + function flush(bytes32 targetDomain) external { + uint256 daiToFlush = batchedDaiToFlush[targetDomain]; + require(daiToFlush > 0, "DomainGuest/zero-dai-flush"); + + batchedDaiToFlush[targetDomain] = 0; + + router.settle(domain, targetDomain, daiToFlush); + + emit Flush(targetDomain, daiToFlush); } } diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index 5027f12..dfa51b2 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -24,6 +24,7 @@ import "src/TeleportConstantFee.sol"; import "./mocks/VatMock.sol"; import "./mocks/DaiMock.sol"; import "./mocks/DaiJoinMock.sol"; +import "./mocks/GatewayMock.sol"; interface Hevm { function warp(uint) external; @@ -51,7 +52,7 @@ contract TeleportJoinTest is DSTest { vat = new VatMock(); dai = new DaiMock(); daiJoin = new DaiJoinMock(address(vat), address(dai)); - join = new TeleportJoin(address(vat), address(daiJoin), ilk, domain); + join = new TeleportJoin(address(vat), address(daiJoin), ilk, domain, address(this)); join.file("line", "l2network", 1_000_000 ether); join.file("vow", vow); join.file("fees", "l2network", address(new TeleportConstantFee(0, TTL))); diff --git a/src/test/integration/TeleportJoin.t.sol b/src/test/integration/TeleportJoin.t.sol index e153851..dd53af1 100644 --- a/src/test/integration/TeleportJoin.t.sol +++ b/src/test/integration/TeleportJoin.t.sol @@ -93,7 +93,7 @@ contract TeleportJoinIntegrationTest is DSTest { function setUp() public { // setup teleportJoin - teleportJoin = new TeleportJoin(address(vat), chainlog.getAddress("MCD_JOIN_DAI"), ILK, MASTER_DOMAIN); + teleportJoin = new TeleportJoin(address(vat), chainlog.getAddress("MCD_JOIN_DAI"), ILK, MASTER_DOMAIN, address(this)); teleportJoin.file(bytes32("vow"), vow); teleportJoin.file("line", SLAVE_DOMAIN, 1_000_000 ether); teleportJoin.file("fees", SLAVE_DOMAIN, address(new TeleportConstantFee(0, TTL))); From 05f20fd8480275faed6878e8c0278c4d3a7a96a8 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Thu, 28 Jul 2022 12:33:04 -0400 Subject: [PATCH 05/35] add initiateTeleport overloads --- src/TeleportJoin.sol | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/TeleportJoin.sol b/src/TeleportJoin.sol index 5c83b3c..82dc7bc 100644 --- a/src/TeleportJoin.sol +++ b/src/TeleportJoin.sol @@ -303,12 +303,37 @@ contract TeleportJoin { emit Settle(sourceDomain, amount); } + function initiateTeleport( + bytes32 targetDomain, + address receiver, + uint128 amount + ) external { + initiateTeleport( + targetDomain, + addressToBytes32(receiver), + amount, + 0 + ); + } + function initiateTeleport( + bytes32 targetDomain, + address receiver, + uint128 amount, + address operator + ) external { + initiateTeleport( + targetDomain, + addressToBytes32(receiver), + amount, + addressToBytes32(operator) + ); + } function initiateTeleport( bytes32 targetDomain, bytes32 receiver, uint128 amount, bytes32 operator - ) external { + ) public { TeleportGUID memory teleport = TeleportGUID({ sourceDomain: domain, targetDomain: targetDomain, From 274baea929813e610d14d45393dc4b05d3c596c2 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Fri, 29 Jul 2022 09:17:19 -0400 Subject: [PATCH 06/35] add natspec; initiateTeleport overloads; adjustments for safety of holding DAI in the contract; added tests for new functionality --- src/TeleportJoin.sol | 67 +++++++++++++---- src/TeleportRouter.sol | 14 ++-- src/test/TeleportJoin.t.sol | 99 +++++++++++++++++++++++-- src/test/integration/TeleportJoin.t.sol | 10 ++- 4 files changed, 160 insertions(+), 30 deletions(-) diff --git a/src/TeleportJoin.sol b/src/TeleportJoin.sol index 82dc7bc..045b3d3 100644 --- a/src/TeleportJoin.sol +++ b/src/TeleportJoin.sol @@ -46,22 +46,23 @@ interface FeesLike { interface GatewayLike { function registerMint(TeleportGUID calldata teleportGUID) external; - function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) external; + function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) external; } // Primary control for extending Teleport credit contract TeleportJoin { - mapping (address => uint256) public wards; // Auth - mapping (bytes32 => address) public fees; // Fees contract per source domain - mapping (bytes32 => uint256) public line; // Debt ceiling per source domain - mapping (bytes32 => int256) public debt; // Outstanding debt per source domain (can be < 0 when settlement occurs before mint) - mapping (bytes32 => TeleportStatus) public teleports; // Approved teleports and pending unpaid - mapping (bytes32 => uint256) public batchedDaiToFlush; // Pending DAI to flush per target domain + mapping (address => uint256) public wards; // Auth + mapping (bytes32 => address) public fees; // Fees contract per source domain + mapping (bytes32 => uint256) public line; // Debt ceiling per source domain + mapping (bytes32 => int256) public debt; // Outstanding debt per source domain (can be < 0 when settlement occurs before mint) + mapping (bytes32 => TeleportStatus) public teleports; // Approved teleports and pending unpaid + mapping (bytes32 => uint256) public batches; // Pending DAI to flush per target domain address public vow; uint256 internal art; // We need to preserve the last art value before the position being skimmed (End) uint80 public nonce; + uint256 public fdust; // The minimum amount of DAI to be flushed per target domain (prevent spam) VatLike immutable public vat; DaiJoinLike immutable public daiJoin; @@ -76,13 +77,14 @@ contract TeleportJoin { event Rely(address indexed usr); event Deny(address indexed usr); event File(bytes32 indexed what, address data); + event File(bytes32 indexed what, uint256 data); event File(bytes32 indexed what, bytes32 indexed domain, address data); event File(bytes32 indexed what, bytes32 indexed domain, uint256 data); event Register(bytes32 indexed hashGUID, TeleportGUID teleportGUID); event Mint( bytes32 indexed hashGUID, TeleportGUID teleportGUID, uint256 amount, uint256 maxFeePercentage, uint256 operatorFee, address originator ); - event Settle(bytes32 indexed sourceDomain, uint256 batchedDaiToFlush); + event Settle(bytes32 indexed sourceDomain, uint256 amount); event InitiateTeleport(TeleportGUID teleport); event Flush(bytes32 indexed targetDomain, uint256 dai); @@ -133,6 +135,15 @@ contract TeleportJoin { emit File(what, data); } + function file(bytes32 what, uint256 data) external auth { + if (what == "fdust") { + fdust = data; + } else { + revert("TeleportJoin/file-unrecognized-param"); + } + emit File(what, data); + } + function file(bytes32 what, bytes32 domain_, address data) external auth { if (what == "fees") { fees[domain_] = data; @@ -286,9 +297,9 @@ contract TeleportJoin { * @param amount Amount of DAI that is being processed for repayment **/ function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) external { - require(msg.sender == address(router), "TeleportJoin/not-router"); require(targetDomain == domain, "TeleportJoin/incorrect-targetDomain"); require(amount <= 2 ** 255, "TeleportJoin/overflow"); + dai.transferFrom(msg.sender, address(this), amount); daiJoin.join(address(this), amount); if (vat.live() == 1) { (, uint256 art_) = vat.urns(ilk, address(this)); // rate == RAY => normalized debt == actual debt @@ -303,6 +314,13 @@ contract TeleportJoin { emit Settle(sourceDomain, amount); } + /** + * @notice Initiate Maker teleport + * @dev Will fire a teleport event, burn the dai and initiate a censorship-resistant slow-path message + * @param targetDomain The target domain to teleport to + * @param receiver The receiver address of the DAI on the target domain + * @param amount The amount of DAI to teleport + **/ function initiateTeleport( bytes32 targetDomain, address receiver, @@ -315,6 +333,15 @@ contract TeleportJoin { 0 ); } + + /** + * @notice Initiate Maker teleport + * @dev Will fire a teleport event, burn the dai and initiate a censorship-resistant slow-path message + * @param targetDomain The target domain to teleport to + * @param receiver The receiver address of the DAI on the target domain + * @param amount The amount of DAI to teleport + * @param operator An optional address that can be used to mint the DAI at the destination domain (useful for automated relays) + **/ function initiateTeleport( bytes32 targetDomain, address receiver, @@ -328,6 +355,15 @@ contract TeleportJoin { addressToBytes32(operator) ); } + + /** + * @notice Initiate Maker teleport + * @dev Will fire a teleport event, burn the dai and initiate a censorship-resistant slow-path message + * @param targetDomain The target domain to teleport to + * @param receiver The receiver address of the DAI on the target domain + * @param amount The amount of DAI to teleport + * @param operator An optional address that can be used to mint the DAI at the destination domain (useful for automated relays) + **/ function initiateTeleport( bytes32 targetDomain, bytes32 receiver, @@ -344,7 +380,7 @@ contract TeleportJoin { timestamp: uint48(block.timestamp) }); - batchedDaiToFlush[targetDomain] += amount; + batches[targetDomain] += amount; require(dai.transferFrom(msg.sender, address(this), amount), "DomainHost/transfer-failed"); // Initiate the censorship-resistant slow-path @@ -354,11 +390,16 @@ contract TeleportJoin { emit InitiateTeleport(teleport); } + /** + * @notice Flush batched DAI to the target domain + * @dev Will initiate a settle operation along the secure, slow routing path + * @param targetDomain The target domain to settle + **/ function flush(bytes32 targetDomain) external { - uint256 daiToFlush = batchedDaiToFlush[targetDomain]; - require(daiToFlush > 0, "DomainGuest/zero-dai-flush"); + uint256 daiToFlush = batches[targetDomain]; + require(daiToFlush > fdust, "DomainGuest/flush-dust"); - batchedDaiToFlush[targetDomain] = 0; + batches[targetDomain] = 0; router.settle(domain, targetDomain, daiToFlush); diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 2bb8656..36ca777 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -20,12 +20,13 @@ import "./TeleportGUID.sol"; import "./utils/EnumerableSet.sol"; interface TokenLike { - function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); + function approve(address, uint256) external returns (bool); + function transferFrom(address, address, uint256) external returns (bool); } interface GatewayLike { function registerMint(TeleportGUID calldata teleportGUID) external; - function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) external; + function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) external; } contract TeleportRouter { @@ -146,9 +147,9 @@ contract TeleportRouter { * The sender must be a supported gateway * @param sourceDomain The domain sending the batch of DAI * @param targetDomain The domain receiving the batch of DAI - * @param batchedDaiToFlush The amount of DAI in the batch + * @param amount The amount of DAI in the batch */ - function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) external { + function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) external { // We trust the parent gateway with any sourceDomain as a compromised parent implies compromised child // Otherwise we restrict passing messages only from the actual source domain require(msg.sender == parent || msg.sender == gateways[sourceDomain], "TeleportRouter/sender-not-gateway"); @@ -157,7 +158,8 @@ contract TeleportRouter { if (gateway == address(0)) gateway = parent; require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); // Forward the DAI to settle to the gateway contract - dai.transferFrom(msg.sender, gateway, batchedDaiToFlush); - GatewayLike(gateway).settle(sourceDomain, targetDomain, batchedDaiToFlush); + dai.transferFrom(msg.sender, address(this), amount); + dai.approve(gateway, amount); + GatewayLike(gateway).settle(sourceDomain, targetDomain, amount); } } diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index dfa51b2..0bd0b38 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -52,7 +52,7 @@ contract TeleportJoinTest is DSTest { vat = new VatMock(); dai = new DaiMock(); daiJoin = new DaiJoinMock(address(vat), address(dai)); - join = new TeleportJoin(address(vat), address(daiJoin), ilk, domain, address(this)); + join = new TeleportJoin(address(vat), address(daiJoin), ilk, domain, address(new GatewayMock())); join.file("line", "l2network", 1_000_000 ether); join.file("vow", vow); join.file("fees", "l2network", address(new TeleportConstantFee(0, TTL))); @@ -87,6 +87,10 @@ contract TeleportJoinTest is DSTest { (ok,) = address(join).call(abi.encodeWithSignature("file(bytes32,address)", what, data)); } + function _tryFile(bytes32 what, uint256 data) internal returns (bool ok) { + (ok,) = address(join).call(abi.encodeWithSignature("file(bytes32,uint256)", what, data)); + } + function _tryFile(bytes32 what, bytes32 domain_, address data) internal returns (bool ok) { (ok,) = address(join).call(abi.encodeWithSignature("file(bytes32,bytes32,address)", what, domain_, data)); } @@ -121,6 +125,10 @@ contract TeleportJoinTest is DSTest { assertTrue(_tryFile("vow", address(888))); assertEq(join.vow(), address(888)); + assertEq(join.fdust(), 0); + assertTrue(_tryFile("fdust", 888)); + assertEq(join.fdust(), 888); + assertEq(join.fees("aaa"), address(0)); assertTrue(_tryFile("fees", "aaa", address(888))); assertEq(join.fees("aaa"), address(888)); @@ -141,10 +149,37 @@ contract TeleportJoinTest is DSTest { function testInvalidWhat() public { assertTrue(!_tryFile("meh", address(888))); + assertTrue(!_tryFile("meh", 888)); assertTrue(!_tryFile("meh", domain, address(888))); assertTrue(!_tryFile("meh", domain, 888)); } + function testRegister() public { + TeleportGUID memory guid = TeleportGUID({ + sourceDomain: "l2network", + targetDomain: "ethereum", + receiver: addressToBytes32(address(123)), + operator: addressToBytes32(address(this)), + amount: 250_000 ether, + nonce: 5, + timestamp: uint48(block.timestamp) + }); + + assertEq(dai.balanceOf(address(123)), 0); + assertTrue(!_blessed(guid)); + assertEq(_pending(guid), 0); + assertEq(_ink(), 0); + assertEq(_art(), 0); + + join.registerMint(guid); + + assertEq(dai.balanceOf(address(123)), 0); + assertTrue(_blessed(guid)); + assertEq(_pending(guid), 250_000 ether); + assertEq(_ink(), 0); + assertEq(_art(), 0); + } + function testRegisterAndWithdrawAll() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", @@ -492,7 +527,8 @@ contract TeleportJoinTest is DSTest { assertEq(join.debt("l2network"), 0); vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(join), 100_000 ether); + daiJoin.exit(address(this), 100_000 ether); + dai.approve(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); @@ -502,7 +538,8 @@ contract TeleportJoinTest is DSTest { function testWithdrawNegativeDebt() public { vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(join), 100_000 ether); + daiJoin.exit(address(this), 100_000 ether); + dai.approve(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); @@ -529,7 +566,8 @@ contract TeleportJoinTest is DSTest { function testWithdrawPartialNegativeDebt() public { vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(join), 100_000 ether); + daiJoin.exit(address(this), 100_000 ether); + dai.approve(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); @@ -558,7 +596,8 @@ contract TeleportJoinTest is DSTest { function testWithdrawVatCaged() public { vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(join), 100_000 ether); + daiJoin.exit(address(this), 100_000 ether); + dai.approve(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); @@ -610,7 +649,8 @@ contract TeleportJoinTest is DSTest { vat.cage(); vat.suck(address(0), address(this), 250_000 * RAD); - daiJoin.exit(address(join), 250_000 ether); + daiJoin.exit(address(this), 250_000 ether); + dai.approve(address(join), 250_000 ether); join.settle("l2network", domain, 250_000 ether); @@ -728,7 +768,8 @@ contract TeleportJoinTest is DSTest { join.file("fees", "l2network_3", address(new TeleportConstantFee(0, TTL))); vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(join), 100_000 ether); + daiJoin.exit(address(this), 100_000 ether); + dai.approve(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); TeleportGUID memory guid = TeleportGUID({ @@ -775,7 +816,8 @@ contract TeleportJoinTest is DSTest { assertEq(join.cure(), 200_000 * RAD); vat.suck(address(0), address(this), 10_000 * RAD); - daiJoin.exit(address(join), 10_000 ether); + daiJoin.exit(address(this), 10_000 ether); + dai.approve(address(join), 100_000 ether); join.settle("l2network_3", domain, 10_000 ether); assertEq(join.debt("l2network"), -50_000 ether); @@ -827,4 +869,45 @@ contract TeleportJoinTest is DSTest { assertEq(_art(), 100_000 ether); assertEq(join.cure(), 100_000 * RAD); } + + function testInitiateTeleport() public { + vat.suck(address(0), address(this), 100_000 * RAD); + daiJoin.exit(address(this), 100_000 ether); + dai.approve(address(join), 100_000 ether); + + assertEq(dai.balanceOf(address(this)), 100_000 ether); + assertEq(join.batches("ethereum"), 0); + assertEq(join.nonce(), 0); + + join.initiateTeleport("ethereum", address(123), 100_000 ether); + + assertEq(dai.balanceOf(address(this)), 0); + assertEq(join.batches("ethereum"), 100_000 ether); + assertEq(join.nonce(), 1); + } + + function testFlush() public { + vat.suck(address(0), address(this), 100_000 * RAD); + daiJoin.exit(address(this), 100_000 ether); + dai.approve(address(join), 100_000 ether); + join.initiateTeleport("ethereum", address(123), 100_000 ether); + + assertEq(join.batches("ethereum"), 100_000 ether); + + join.flush("ethereum"); + + assertEq(join.batches("ethereum"), 0); + } + + function testFailFlushDust() public { + vat.suck(address(0), address(this), 100_000 * RAD); + daiJoin.exit(address(this), 100_000 ether); + dai.approve(address(join), 100_000 ether); + join.initiateTeleport("ethereum", address(123), 100_000 ether); + + assertEq(join.batches("ethereum"), 100_000 ether); + + join.file("fdust", 200_000 ether); + join.flush("ethereum"); + } } diff --git a/src/test/integration/TeleportJoin.t.sol b/src/test/integration/TeleportJoin.t.sol index dd53af1..7b077e5 100644 --- a/src/test/integration/TeleportJoin.t.sol +++ b/src/test/integration/TeleportJoin.t.sol @@ -22,6 +22,8 @@ import {TeleportJoin} from "src/TeleportJoin.sol"; import "src/TeleportGUID.sol"; import "src/TeleportConstantFee.sol"; +import "../mocks/GatewayMock.sol"; + interface Hevm { function warp(uint) external; function store(address, bytes32, bytes32) external; @@ -58,7 +60,8 @@ interface CureLike { } interface TokenLike { - function transfer(address _to, uint256 _value) external returns (bool success); + function transfer(address _to, uint256 _value) external returns (bool success); + function approve(address, uint256) external returns (bool); } contract TeleportJoinIntegrationTest is DSTest { @@ -93,7 +96,7 @@ contract TeleportJoinIntegrationTest is DSTest { function setUp() public { // setup teleportJoin - teleportJoin = new TeleportJoin(address(vat), chainlog.getAddress("MCD_JOIN_DAI"), ILK, MASTER_DOMAIN, address(this)); + teleportJoin = new TeleportJoin(address(vat), chainlog.getAddress("MCD_JOIN_DAI"), ILK, MASTER_DOMAIN, address(new GatewayMock())); teleportJoin.file(bytes32("vow"), vow); teleportJoin.file("line", SLAVE_DOMAIN, 1_000_000 ether); teleportJoin.file("fees", SLAVE_DOMAIN, address(new TeleportConstantFee(0, TTL))); @@ -140,7 +143,8 @@ contract TeleportJoinIntegrationTest is DSTest { // attempt to settle the dai debt assertEq(vat.dai(address(teleportJoin)), 0); - dai.transfer(address(teleportJoin), teleportAmount); + dai.transfer(address(this), teleportAmount); + dai.approve(address(teleportJoin), teleportAmount); teleportJoin.settle(SLAVE_DOMAIN, MASTER_DOMAIN, teleportAmount); From efb16eba095611c0315fa7ae12e3ed2e68aaf186 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Thu, 4 Aug 2022 10:01:22 -0400 Subject: [PATCH 07/35] split common code into _register() --- src/TeleportJoin.sol | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/TeleportJoin.sol b/src/TeleportJoin.sol index 986fd8a..4fb4371 100644 --- a/src/TeleportJoin.sol +++ b/src/TeleportJoin.sol @@ -170,6 +170,21 @@ contract TeleportJoin { cure_ = art * RAY; } + /** + * @dev Internal function that registers a teleport + * @param teleportGUID Struct which contains the whole teleport data + * @param hashGUID Hash of the prev struct + **/ + function _register( + TeleportGUID calldata teleportGUID, + bytes32 hashGUID + ) internal { + require(!teleports[hashGUID].blessed, "TeleportJoin/already-blessed"); + teleports[hashGUID].blessed = true; + teleports[hashGUID].pending = teleportGUID.amount; + emit Register(hashGUID, teleportGUID); + } + /** * @dev Internal function that executes the mint after a teleport is registered * @param teleportGUID Struct which contains the whole teleport data @@ -244,11 +259,7 @@ contract TeleportJoin { function registerMint( TeleportGUID calldata teleportGUID ) external auth { - bytes32 hashGUID = getGUIDHash(teleportGUID); - require(!teleports[hashGUID].blessed, "TeleportJoin/already-blessed"); - teleports[hashGUID].blessed = true; - teleports[hashGUID].pending = teleportGUID.amount; - emit Register(hashGUID, teleportGUID); + _register(teleportGUID, getGUIDHash(teleportGUID)); } /** @@ -265,10 +276,7 @@ contract TeleportJoin { uint256 operatorFee ) external auth returns (uint256 postFeeAmount, uint256 totalFee) { bytes32 hashGUID = getGUIDHash(teleportGUID); - require(!teleports[hashGUID].blessed, "TeleportJoin/already-blessed"); - teleports[hashGUID].blessed = true; - teleports[hashGUID].pending = teleportGUID.amount; - emit Register(hashGUID, teleportGUID); + _register(teleportGUID, hashGUID); (postFeeAmount, totalFee) = _mint(teleportGUID, hashGUID, maxFeePercentage, operatorFee); } From fc059a58cf86b90071900e834598dfb6be45e107 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Thu, 4 Aug 2022 11:27:59 -0400 Subject: [PATCH 08/35] rename parent to defaultGateway --- src/TeleportRouter.sol | 22 +++++++++++----------- src/test/TeleportRouter.t.sol | 18 +++++++++--------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index d74ee79..05c6c05 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -37,7 +37,7 @@ contract TeleportRouter { mapping (bytes32 => address) public gateways; // GatewayLike contracts called by the router for each domain EnumerableSet.Bytes32Set private allDomains; - address public parent; + address public defaultGateway; TokenLike immutable public dai; // L1 DAI ERC20 token @@ -103,14 +103,14 @@ contract TeleportRouter { } /** - * @notice Allows auth to configure the router. The only supported operation is "parent", + * @notice Allows auth to configure the router. The only supported operation is "defaultGateway", * which sets the fallback address if no specific domain is matched. - * @param what The name of the operation. Only "parent" is supported. + * @param what The name of the operation. Only "defaultGateway" is supported. * @param data Set the fallback gateway or address(0) to disable the fallback. */ function file(bytes32 what, address data) external auth { - if (what == "parent") { - parent = data; + if (what == "defaultGateway") { + defaultGateway = data; } else { revert("TeleportRouter/file-unrecognized-param"); } @@ -132,12 +132,12 @@ contract TeleportRouter { * @param teleportGUID The teleport GUID to register */ function registerMint(TeleportGUID calldata teleportGUID) external { - // We trust the parent gateway with any sourceDomain as a compromised parent implies compromised child + // We trust the defaultGateway gateway with any sourceDomain as a compromised defaultGateway implies compromised child // Otherwise we restrict passing messages only from the actual source domain - require(msg.sender == parent || msg.sender == gateways[teleportGUID.sourceDomain], "TeleportRouter/sender-not-gateway"); + require(msg.sender == defaultGateway || msg.sender == gateways[teleportGUID.sourceDomain], "TeleportRouter/sender-not-gateway"); address gateway = gateways[teleportGUID.targetDomain]; // Use fallback if no gateway is configured for the target domain - if (gateway == address(0)) gateway = parent; + if (gateway == address(0)) gateway = defaultGateway; require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); GatewayLike(gateway).registerMint(teleportGUID); } @@ -150,12 +150,12 @@ contract TeleportRouter { * @param amount The amount of DAI in the batch */ function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) external { - // We trust the parent gateway with any sourceDomain as a compromised parent implies compromised child + // We trust the defaultGateway gateway with any sourceDomain as a compromised defaultGateway implies compromised child // Otherwise we restrict passing messages only from the actual source domain - require(msg.sender == parent || msg.sender == gateways[sourceDomain], "TeleportRouter/sender-not-gateway"); + require(msg.sender == defaultGateway || msg.sender == gateways[sourceDomain], "TeleportRouter/sender-not-gateway"); address gateway = gateways[targetDomain]; // Use fallback if no gateway is configured for the target domain - if (gateway == address(0)) gateway = parent; + if (gateway == address(0)) gateway = defaultGateway; require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); // Forward the DAI to settle to the gateway contract dai.transferFrom(msg.sender, address(this), amount); diff --git a/src/test/TeleportRouter.t.sol b/src/test/TeleportRouter.t.sol index b56bdbf..8e3b27a 100644 --- a/src/test/TeleportRouter.t.sol +++ b/src/test/TeleportRouter.t.sol @@ -74,10 +74,10 @@ contract TeleportRouterTest is DSTest { function testFileFailsWhenNotAuthed() public { assertTrue(_tryFile("gateway", "dom", address(888))); - assertTrue(_tryFile("parent", address(888))); + assertTrue(_tryFile("defaultGateway", address(888))); router.deny(address(this)); assertTrue(!_tryFile("gateway", "dom", address(888))); - assertTrue(!_tryFile("parent", address(888))); + assertTrue(!_tryFile("defaultGateway", address(888))); } function testFileNewDomains() public { @@ -244,10 +244,10 @@ contract TeleportRouterTest is DSTest { assertEq(router.domainAt(0), domain2); } - function testFileParent() public { - assertTrue(_tryFile("parent", address(123))); + function testFileDefaultGateway() public { + assertTrue(_tryFile("defaultGateway", address(123))); - assertEq(router.parent(), address(123)); + assertEq(router.defaultGateway(), address(123)); } function testFileInvalidWhat() public { @@ -317,7 +317,7 @@ contract TeleportRouterTest is DSTest { router.registerMint(guid); } - function testRegisterMintFromParent() public { + function testRegisterMintFromDefaultGateway() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", targetDomain: "another-l2network", @@ -327,7 +327,7 @@ contract TeleportRouterTest is DSTest { nonce: 5, timestamp: uint48(block.timestamp) }); - router.file("parent", address(this)); + router.file("defaultGateway", address(this)); router.file("gateway", "another-l2network", address(new GatewayMock())); router.registerMint(guid); @@ -359,8 +359,8 @@ contract TeleportRouterTest is DSTest { router.settle("l2network", "another-l2network", 100 ether); } - function testSettleFromParent() public { - router.file("parent", address(this)); + function testSettleFromDefaultGateway() public { + router.file("defaultGateway", address(this)); router.file("gateway", "another-l2network", address(new GatewayMock())); DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); From 6b7c024c5e39d60bff5c84d2d03cb57ba3220376 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Thu, 4 Aug 2022 11:33:52 -0400 Subject: [PATCH 09/35] infinite approve the dai on new gateways --- src/TeleportRouter.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 05c6c05..6016425 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -96,6 +96,9 @@ contract TeleportRouter { } gateways[domain] = data; + if (data != address(0)) { + dai.approve(data, type(uint256).max); + } } else { revert("TeleportRouter/file-unrecognized-param"); } @@ -111,6 +114,7 @@ contract TeleportRouter { function file(bytes32 what, address data) external auth { if (what == "defaultGateway") { defaultGateway = data; + dai.approve(data, type(uint256).max); } else { revert("TeleportRouter/file-unrecognized-param"); } @@ -159,7 +163,6 @@ contract TeleportRouter { require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); // Forward the DAI to settle to the gateway contract dai.transferFrom(msg.sender, address(this), amount); - dai.approve(gateway, amount); GatewayLike(gateway).settle(sourceDomain, targetDomain, amount); } } From 1ba0728e95638d86d0eb9f0ae48e69b7adc24e7a Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Thu, 4 Aug 2022 11:36:44 -0400 Subject: [PATCH 10/35] fix natspec --- src/TeleportJoin.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TeleportJoin.sol b/src/TeleportJoin.sol index 4fb4371..c007964 100644 --- a/src/TeleportJoin.sol +++ b/src/TeleportJoin.sol @@ -299,7 +299,7 @@ contract TeleportJoin { } /** - * @dev External function that repays debt with DAI previously pushed to this contract (in general coming from the bridges) + * @dev External function that repays debt with DAI pulled from the caller * @param sourceDomain domain where the DAI is coming from * @param targetDomain this domain * @param amount Amount of DAI that is being processed for repayment From fb1dacdde0eb436ae907c03a9d28353e6d81856e Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Fri, 30 Sep 2022 11:45:12 -0400 Subject: [PATCH 11/35] moved outbound teleport to the router --- src/TeleportJoin.sol | 113 +------------------ src/TeleportRouter.sol | 143 ++++++++++++++++++++++-- src/test/TeleportJoin.t.sol | 67 ++--------- src/test/TeleportRouter.t.sol | 64 ++++++++++- src/test/integration/TeleportJoin.t.sol | 7 +- 5 files changed, 203 insertions(+), 191 deletions(-) diff --git a/src/TeleportJoin.sol b/src/TeleportJoin.sol index c007964..b1be0dd 100644 --- a/src/TeleportJoin.sol +++ b/src/TeleportJoin.sol @@ -56,20 +56,16 @@ contract TeleportJoin { mapping (bytes32 => uint256) public line; // Debt ceiling per source domain mapping (bytes32 => int256) public debt; // Outstanding debt per source domain (can be < 0 when settlement occurs before mint) mapping (bytes32 => TeleportStatus) public teleports; // Approved teleports and pending unpaid - mapping (bytes32 => uint256) public batches; // Pending DAI to flush per target domain address public vow; uint256 internal art; // We need to preserve the last art value before the position being skimmed (End) - uint80 public nonce; - uint256 public fdust; // The minimum amount of DAI to be flushed per target domain (prevent spam) VatLike immutable public vat; DaiJoinLike immutable public daiJoin; TokenLike immutable public dai; bytes32 immutable public ilk; bytes32 immutable public domain; - GatewayLike immutable public router; uint256 constant public WAD = 10 ** 18; uint256 constant public RAY = 10 ** 27; @@ -77,7 +73,6 @@ contract TeleportJoin { event Rely(address indexed usr); event Deny(address indexed usr); event File(bytes32 indexed what, address data); - event File(bytes32 indexed what, uint256 data); event File(bytes32 indexed what, bytes32 indexed domain, address data); event File(bytes32 indexed what, bytes32 indexed domain, uint256 data); event Register(bytes32 indexed hashGUID, TeleportGUID teleportGUID); @@ -85,15 +80,13 @@ contract TeleportJoin { bytes32 indexed hashGUID, TeleportGUID teleportGUID, uint256 amount, uint256 maxFeePercentage, uint256 operatorFee, address originator ); event Settle(bytes32 indexed sourceDomain, uint256 amount); - event InitiateTeleport(TeleportGUID teleport); - event Flush(bytes32 indexed targetDomain, uint256 dai); struct TeleportStatus { bool blessed; uint248 pending; } - constructor(address vat_, address daiJoin_, bytes32 ilk_, bytes32 domain_, address router_) { + constructor(address vat_, address daiJoin_, bytes32 ilk_, bytes32 domain_) { wards[msg.sender] = 1; emit Rely(msg.sender); vat = VatLike(vat_); @@ -103,8 +96,6 @@ contract TeleportJoin { dai.approve(daiJoin_, type(uint256).max); ilk = ilk_; domain = domain_; - router = GatewayLike(router_); - dai.approve(router_, type(uint256).max); } function _min(uint256 x, uint256 y) internal pure returns (uint256 z) { @@ -135,15 +126,6 @@ contract TeleportJoin { emit File(what, data); } - function file(bytes32 what, uint256 data) external auth { - if (what == "fdust") { - fdust = data; - } else { - revert("TeleportJoin/file-unrecognized-param"); - } - emit File(what, data); - } - function file(bytes32 what, bytes32 domain_, address data) external auth { if (what == "fees") { fees[domain_] = data; @@ -307,7 +289,6 @@ contract TeleportJoin { function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) external { require(targetDomain == domain, "TeleportJoin/incorrect-targetDomain"); require(amount <= uint256(type(int256).max), "TeleportJoin/overflow"); - dai.transferFrom(msg.sender, address(this), amount); daiJoin.join(address(this), amount); if (vat.live() == 1) { (, uint256 art_) = vat.urns(ilk, address(this)); // rate == RAY => normalized debt == actual debt @@ -321,96 +302,4 @@ contract TeleportJoin { debt[sourceDomain] -= int256(amount); emit Settle(sourceDomain, amount); } - - /** - * @notice Initiate Maker teleport - * @dev Will fire a teleport event, burn the dai and initiate a censorship-resistant slow-path message - * @param targetDomain The target domain to teleport to - * @param receiver The receiver address of the DAI on the target domain - * @param amount The amount of DAI to teleport - **/ - function initiateTeleport( - bytes32 targetDomain, - address receiver, - uint128 amount - ) external { - initiateTeleport( - targetDomain, - addressToBytes32(receiver), - amount, - 0 - ); - } - - /** - * @notice Initiate Maker teleport - * @dev Will fire a teleport event, burn the dai and initiate a censorship-resistant slow-path message - * @param targetDomain The target domain to teleport to - * @param receiver The receiver address of the DAI on the target domain - * @param amount The amount of DAI to teleport - * @param operator An optional address that can be used to mint the DAI at the destination domain (useful for automated relays) - **/ - function initiateTeleport( - bytes32 targetDomain, - address receiver, - uint128 amount, - address operator - ) external { - initiateTeleport( - targetDomain, - addressToBytes32(receiver), - amount, - addressToBytes32(operator) - ); - } - - /** - * @notice Initiate Maker teleport - * @dev Will fire a teleport event, burn the dai and initiate a censorship-resistant slow-path message - * @param targetDomain The target domain to teleport to - * @param receiver The receiver address of the DAI on the target domain - * @param amount The amount of DAI to teleport - * @param operator An optional address that can be used to mint the DAI at the destination domain (useful for automated relays) - **/ - function initiateTeleport( - bytes32 targetDomain, - bytes32 receiver, - uint128 amount, - bytes32 operator - ) public { - TeleportGUID memory teleport = TeleportGUID({ - sourceDomain: domain, - targetDomain: targetDomain, - receiver: receiver, - operator: operator, - amount: amount, - nonce: nonce++, - timestamp: uint48(block.timestamp) - }); - - batches[targetDomain] += amount; - require(dai.transferFrom(msg.sender, address(this), amount), "DomainHost/transfer-failed"); - - // Initiate the censorship-resistant slow-path - router.registerMint(teleport); - - // Oracle listens to this event for the fast-path - emit InitiateTeleport(teleport); - } - - /** - * @notice Flush batched DAI to the target domain - * @dev Will initiate a settle operation along the secure, slow routing path - * @param targetDomain The target domain to settle - **/ - function flush(bytes32 targetDomain) external { - uint256 daiToFlush = batches[targetDomain]; - require(daiToFlush > fdust, "DomainGuest/flush-dust"); - - batches[targetDomain] = 0; - - router.settle(domain, targetDomain, daiToFlush); - - emit Flush(targetDomain, daiToFlush); - } } diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 6016425..103e277 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -35,24 +35,32 @@ contract TeleportRouter { mapping (address => uint256) public wards; // Auth mapping (bytes32 => address) public gateways; // GatewayLike contracts called by the router for each domain + mapping (bytes32 => uint256) public batches; // Pending DAI to flush per target domain EnumerableSet.Bytes32Set private allDomains; address public defaultGateway; + uint80 public nonce; + uint256 public fdust; // The minimum amount of DAI to be flushed per target domain (prevent spam) - TokenLike immutable public dai; // L1 DAI ERC20 token + TokenLike immutable public dai; + bytes32 immutable public domain; event Rely(address indexed usr); event Deny(address indexed usr); event File(bytes32 indexed what, bytes32 indexed domain, address data); event File(bytes32 indexed what, address data); + event File(bytes32 indexed what, uint256 data); + event InitiateTeleport(TeleportGUID teleport); + event Flush(bytes32 indexed targetDomain, uint256 dai); modifier auth { require(wards[msg.sender] == 1, "TeleportRouter/not-authorized"); _; } - constructor(address dai_) { + constructor(address dai_, bytes32 domain_) { dai = TokenLike(dai_); + domain = domain_; wards[msg.sender] = 1; emit Rely(msg.sender); } @@ -76,33 +84,33 @@ contract TeleportRouter { * @dev In addition to updating the mapping `gateways` which maps GatewayLike contracts to domain names this method * also maintains the enumerable set `allDomains`. * @param what The name of the operation. Only "gateway" is supported. - * @param domain The domain for which a GatewayLike contract is added, replaced or removed. + * @param _domain The domain for which a GatewayLike contract is added, replaced or removed. * @param data The address of the GatewayLike contract to install for the domain (or address(0) to remove a domain) */ - function file(bytes32 what, bytes32 domain, address data) external auth { + function file(bytes32 what, bytes32 _domain, address data) external auth { if (what == "gateway") { - address prevGateway = gateways[domain]; + address prevGateway = gateways[_domain]; if(prevGateway == address(0)) { // new domain => add it to allDomains if(data != address(0)) { - allDomains.add(domain); + allDomains.add(_domain); } } else { // existing domain if(data == address(0)) { // => remove domain from allDomains - allDomains.remove(domain); + allDomains.remove(_domain); } } - gateways[domain] = data; + gateways[_domain] = data; if (data != address(0)) { dai.approve(data, type(uint256).max); } } else { revert("TeleportRouter/file-unrecognized-param"); } - emit File(what, domain, data); + emit File(what, _domain, data); } /** @@ -120,6 +128,15 @@ contract TeleportRouter { } emit File(what, data); } + + function file(bytes32 what, uint256 data) external auth { + if (what == "fdust") { + fdust = data; + } else { + revert("TeleportJoin/file-unrecognized-param"); + } + emit File(what, data); + } function numDomains() external view returns (uint256) { return allDomains.length(); @@ -127,8 +144,8 @@ contract TeleportRouter { function domainAt(uint256 index) external view returns (bytes32) { return allDomains.at(index); } - function hasDomain(bytes32 domain) external view returns (bool) { - return allDomains.contains(domain); + function hasDomain(bytes32 _domain) external view returns (bool) { + return allDomains.contains(_domain); } /** @@ -139,6 +156,11 @@ contract TeleportRouter { // We trust the defaultGateway gateway with any sourceDomain as a compromised defaultGateway implies compromised child // Otherwise we restrict passing messages only from the actual source domain require(msg.sender == defaultGateway || msg.sender == gateways[teleportGUID.sourceDomain], "TeleportRouter/sender-not-gateway"); + + _registerMint(teleportGUID); + } + + function _registerMint(TeleportGUID memory teleportGUID) internal { address gateway = gateways[teleportGUID.targetDomain]; // Use fallback if no gateway is configured for the target domain if (gateway == address(0)) gateway = defaultGateway; @@ -157,12 +179,109 @@ contract TeleportRouter { // We trust the defaultGateway gateway with any sourceDomain as a compromised defaultGateway implies compromised child // Otherwise we restrict passing messages only from the actual source domain require(msg.sender == defaultGateway || msg.sender == gateways[sourceDomain], "TeleportRouter/sender-not-gateway"); + + _settle(msg.sender, sourceDomain, targetDomain, amount); + } + + function _settle(address from, bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) internal { address gateway = gateways[targetDomain]; // Use fallback if no gateway is configured for the target domain if (gateway == address(0)) gateway = defaultGateway; require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); // Forward the DAI to settle to the gateway contract - dai.transferFrom(msg.sender, address(this), amount); + dai.transferFrom(from, gateway, amount); GatewayLike(gateway).settle(sourceDomain, targetDomain, amount); } + + /** + * @notice Initiate Maker teleport + * @dev Will fire a teleport event, burn the dai and initiate a censorship-resistant slow-path message + * @param targetDomain The target domain to teleport to + * @param receiver The receiver address of the DAI on the target domain + * @param amount The amount of DAI to teleport + **/ + function initiateTeleport( + bytes32 targetDomain, + address receiver, + uint128 amount + ) external { + initiateTeleport( + targetDomain, + addressToBytes32(receiver), + amount, + 0 + ); + } + + /** + * @notice Initiate Maker teleport + * @dev Will fire a teleport event, burn the dai and initiate a censorship-resistant slow-path message + * @param targetDomain The target domain to teleport to + * @param receiver The receiver address of the DAI on the target domain + * @param amount The amount of DAI to teleport + * @param operator An optional address that can be used to mint the DAI at the destination domain (useful for automated relays) + **/ + function initiateTeleport( + bytes32 targetDomain, + address receiver, + uint128 amount, + address operator + ) external { + initiateTeleport( + targetDomain, + addressToBytes32(receiver), + amount, + addressToBytes32(operator) + ); + } + + /** + * @notice Initiate Maker teleport + * @dev Will fire a teleport event, burn the dai and initiate a censorship-resistant slow-path message + * @param targetDomain The target domain to teleport to + * @param receiver The receiver address of the DAI on the target domain + * @param amount The amount of DAI to teleport + * @param operator An optional address that can be used to mint the DAI at the destination domain (useful for automated relays) + **/ + function initiateTeleport( + bytes32 targetDomain, + bytes32 receiver, + uint128 amount, + bytes32 operator + ) public { + TeleportGUID memory teleport = TeleportGUID({ + sourceDomain: domain, + targetDomain: targetDomain, + receiver: receiver, + operator: operator, + amount: amount, + nonce: nonce++, + timestamp: uint48(block.timestamp) + }); + + batches[targetDomain] += amount; + require(dai.transferFrom(msg.sender, address(this), amount), "DomainHost/transfer-failed"); + + // Initiate the censorship-resistant slow-path + _registerMint(teleport); + + // Oracle listens to this event for the fast-path + emit InitiateTeleport(teleport); + } + + /** + * @notice Flush batched DAI to the target domain + * @dev Will initiate a settle operation along the secure, slow routing path + * @param targetDomain The target domain to settle + **/ + function flush(bytes32 targetDomain) external { + uint256 daiToFlush = batches[targetDomain]; + require(daiToFlush > fdust, "DomainGuest/flush-dust"); + + batches[targetDomain] = 0; + + _settle(address(this), domain, targetDomain, daiToFlush); + + emit Flush(targetDomain, daiToFlush); + } } diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index 7b84e71..6a03068 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -24,7 +24,6 @@ import "src/TeleportConstantFee.sol"; import "./mocks/VatMock.sol"; import "./mocks/DaiMock.sol"; import "./mocks/DaiJoinMock.sol"; -import "./mocks/GatewayMock.sol"; interface Hevm { function warp(uint) external; @@ -52,7 +51,7 @@ contract TeleportJoinTest is DSTest { vat = new VatMock(); dai = new DaiMock(); daiJoin = new DaiJoinMock(address(vat), address(dai)); - join = new TeleportJoin(address(vat), address(daiJoin), ilk, domain, address(new GatewayMock())); + join = new TeleportJoin(address(vat), address(daiJoin), ilk, domain); join.file("line", "l2network", 1_000_000 ether); join.file("vow", vow); join.file("fees", "l2network", address(new TeleportConstantFee(0, TTL))); @@ -87,10 +86,6 @@ contract TeleportJoinTest is DSTest { (ok,) = address(join).call(abi.encodeWithSignature("file(bytes32,address)", what, data)); } - function _tryFile(bytes32 what, uint256 data) internal returns (bool ok) { - (ok,) = address(join).call(abi.encodeWithSignature("file(bytes32,uint256)", what, data)); - } - function _tryFile(bytes32 what, bytes32 domain_, address data) internal returns (bool ok) { (ok,) = address(join).call(abi.encodeWithSignature("file(bytes32,bytes32,address)", what, domain_, data)); } @@ -125,10 +120,6 @@ contract TeleportJoinTest is DSTest { assertTrue(_tryFile("vow", address(888))); assertEq(join.vow(), address(888)); - assertEq(join.fdust(), 0); - assertTrue(_tryFile("fdust", 888)); - assertEq(join.fdust(), 888); - assertEq(join.fees("aaa"), address(0)); assertTrue(_tryFile("fees", "aaa", address(888))); assertEq(join.fees("aaa"), address(888)); @@ -149,7 +140,6 @@ contract TeleportJoinTest is DSTest { function testInvalidWhat() public { assertTrue(!_tryFile("meh", address(888))); - assertTrue(!_tryFile("meh", 888)); assertTrue(!_tryFile("meh", domain, address(888))); assertTrue(!_tryFile("meh", domain, 888)); } @@ -528,7 +518,7 @@ contract TeleportJoinTest is DSTest { vat.suck(address(0), address(this), 100_000 * RAD); daiJoin.exit(address(this), 100_000 ether); - dai.approve(address(join), 100_000 ether); + dai.transfer(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); @@ -539,7 +529,7 @@ contract TeleportJoinTest is DSTest { function testWithdrawNegativeDebt() public { vat.suck(address(0), address(this), 100_000 * RAD); daiJoin.exit(address(this), 100_000 ether); - dai.approve(address(join), 100_000 ether); + dai.transfer(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); @@ -567,7 +557,7 @@ contract TeleportJoinTest is DSTest { function testWithdrawPartialNegativeDebt() public { vat.suck(address(0), address(this), 100_000 * RAD); daiJoin.exit(address(this), 100_000 ether); - dai.approve(address(join), 100_000 ether); + dai.transfer(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); @@ -597,7 +587,7 @@ contract TeleportJoinTest is DSTest { function testWithdrawVatCaged() public { vat.suck(address(0), address(this), 100_000 * RAD); daiJoin.exit(address(this), 100_000 ether); - dai.approve(address(join), 100_000 ether); + dai.transfer(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); @@ -650,7 +640,7 @@ contract TeleportJoinTest is DSTest { vat.suck(address(0), address(this), 250_000 * RAD); daiJoin.exit(address(this), 250_000 ether); - dai.approve(address(join), 250_000 ether); + dai.transfer(address(join), 250_000 ether); join.settle("l2network", domain, 250_000 ether); @@ -769,7 +759,7 @@ contract TeleportJoinTest is DSTest { vat.suck(address(0), address(this), 100_000 * RAD); daiJoin.exit(address(this), 100_000 ether); - dai.approve(address(join), 100_000 ether); + dai.transfer(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); TeleportGUID memory guid = TeleportGUID({ @@ -817,7 +807,7 @@ contract TeleportJoinTest is DSTest { vat.suck(address(0), address(this), 10_000 * RAD); daiJoin.exit(address(this), 10_000 ether); - dai.approve(address(join), 100_000 ether); + dai.transfer(address(join), 10_000 ether); join.settle("l2network_3", domain, 10_000 ether); assertEq(join.debt("l2network"), -50_000 ether); @@ -869,45 +859,4 @@ contract TeleportJoinTest is DSTest { assertEq(_art(), 100_000 ether); assertEq(join.cure(), 100_000 * RAD); } - - function testInitiateTeleport() public { - vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(this), 100_000 ether); - dai.approve(address(join), 100_000 ether); - - assertEq(dai.balanceOf(address(this)), 100_000 ether); - assertEq(join.batches("ethereum"), 0); - assertEq(join.nonce(), 0); - - join.initiateTeleport("ethereum", address(123), 100_000 ether); - - assertEq(dai.balanceOf(address(this)), 0); - assertEq(join.batches("ethereum"), 100_000 ether); - assertEq(join.nonce(), 1); - } - - function testFlush() public { - vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(this), 100_000 ether); - dai.approve(address(join), 100_000 ether); - join.initiateTeleport("ethereum", address(123), 100_000 ether); - - assertEq(join.batches("ethereum"), 100_000 ether); - - join.flush("ethereum"); - - assertEq(join.batches("ethereum"), 0); - } - - function testFailFlushDust() public { - vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(this), 100_000 ether); - dai.approve(address(join), 100_000 ether); - join.initiateTeleport("ethereum", address(123), 100_000 ether); - - assertEq(join.batches("ethereum"), 100_000 ether); - - join.file("fdust", 200_000 ether); - join.flush("ethereum"); - } } diff --git a/src/test/TeleportRouter.t.sol b/src/test/TeleportRouter.t.sol index 8e3b27a..e9c647a 100644 --- a/src/test/TeleportRouter.t.sol +++ b/src/test/TeleportRouter.t.sol @@ -35,7 +35,7 @@ contract TeleportRouterTest is DSTest { function setUp() public { dai = address(new DaiMock()); teleportJoin = address(new GatewayMock()); - router = new TeleportRouter(dai); + router = new TeleportRouter(dai, l1Domain); } function _tryRely(address usr) internal returns (bool ok) { @@ -54,8 +54,13 @@ contract TeleportRouterTest is DSTest { (ok,) = address(router).call(abi.encodeWithSignature("file(bytes32,address)", what, data)); } + function _tryFile(bytes32 what, uint256 data) internal returns (bool ok) { + (ok,) = address(router).call(abi.encodeWithSignature("file(bytes32,uint256)", what, data)); + } + function testConstructor() public { assertEq(address(router.dai()), dai); + assertEq(router.domain(), l1Domain); assertEq(router.wards(address(this)), 1); } @@ -244,10 +249,14 @@ contract TeleportRouterTest is DSTest { assertEq(router.domainAt(0), domain2); } - function testFileDefaultGateway() public { + function testFile() public { + assertEq(router.defaultGateway(), address(0)); assertTrue(_tryFile("defaultGateway", address(123))); - assertEq(router.defaultGateway(), address(123)); + + assertEq(router.fdust(), 0); + assertTrue(_tryFile("fdust", 888)); + assertEq(router.fdust(), 888); } function testFileInvalidWhat() public { @@ -373,4 +382,53 @@ contract TeleportRouterTest is DSTest { router.settle("l2network", "invalid-network", 100 ether); } + + function testInitiateTeleport() public { + router.file("defaultGateway", teleportJoin); + DaiMock(dai).mint(address(this), 100_000 ether); + DaiMock(dai).approve(address(router), 100_000 ether); + + assertEq(DaiMock(dai).balanceOf(address(this)), 100_000 ether); + assertEq(DaiMock(dai).balanceOf(address(router)), 0); + assertEq(router.batches("ethereum"), 0); + assertEq(router.nonce(), 0); + + router.initiateTeleport("ethereum", address(123), 100_000 ether); + + assertEq(DaiMock(dai).balanceOf(address(this)), 0); + assertEq(DaiMock(dai).balanceOf(address(router)), 100_000 ether); + assertEq(router.batches("ethereum"), 100_000 ether); + assertEq(router.nonce(), 1); + } + + function testFlush() public { + router.file("defaultGateway", teleportJoin); + DaiMock(dai).mint(address(this), 100_000 ether); + DaiMock(dai).approve(address(router), 100_000 ether); + router.initiateTeleport("ethereum", address(123), 100_000 ether); + + assertEq(router.batches("ethereum"), 100_000 ether); + assertEq(DaiMock(dai).balanceOf(address(router)), 100_000 ether); + assertEq(DaiMock(dai).balanceOf(teleportJoin), 0); + + router.flush("ethereum"); + + assertEq(router.batches("ethereum"), 0); + assertEq(DaiMock(dai).balanceOf(address(router)), 0); + assertEq(DaiMock(dai).balanceOf(teleportJoin), 100_000 ether); + } + + function testFailFlushDust() public { + router.file("defaultGateway", teleportJoin); + DaiMock(dai).mint(address(this), 100_000 ether); + DaiMock(dai).approve(address(router), 100_000 ether); + router.initiateTeleport("ethereum", address(123), 100_000 ether); + + assertEq(router.batches("ethereum"), 100_000 ether); + assertEq(DaiMock(dai).balanceOf(address(router)), 100_000 ether); + assertEq(DaiMock(dai).balanceOf(teleportJoin), 0); + + router.file("fdust", 200_000 ether); + router.flush("ethereum"); + } } diff --git a/src/test/integration/TeleportJoin.t.sol b/src/test/integration/TeleportJoin.t.sol index 449e6c3..6d5fec1 100644 --- a/src/test/integration/TeleportJoin.t.sol +++ b/src/test/integration/TeleportJoin.t.sol @@ -22,8 +22,6 @@ import {TeleportJoin} from "src/TeleportJoin.sol"; import "src/TeleportGUID.sol"; import "src/TeleportConstantFee.sol"; -import "../mocks/GatewayMock.sol"; - interface Hevm { function warp(uint) external; function store(address, bytes32, bytes32) external; @@ -96,7 +94,7 @@ contract TeleportJoinIntegrationTest is DSTest { function setUp() public { // setup teleportJoin - teleportJoin = new TeleportJoin(address(vat), chainlog.getAddress("MCD_JOIN_DAI"), ILK, MASTER_DOMAIN, address(new GatewayMock())); + teleportJoin = new TeleportJoin(address(vat), chainlog.getAddress("MCD_JOIN_DAI"), ILK, MASTER_DOMAIN); teleportJoin.file(bytes32("vow"), vow); teleportJoin.file("line", SLAVE_DOMAIN, 1_000_000 ether); teleportJoin.file("fees", SLAVE_DOMAIN, address(new TeleportConstantFee(0, TTL))); @@ -143,8 +141,7 @@ contract TeleportJoinIntegrationTest is DSTest { // attempt to settle the dai debt assertEq(vat.dai(address(teleportJoin)), 0); - dai.transfer(address(this), teleportAmount); - dai.approve(address(teleportJoin), teleportAmount); + dai.transfer(address(teleportJoin), teleportAmount); teleportJoin.settle(SLAVE_DOMAIN, MASTER_DOMAIN, teleportAmount); From 9fab6efa4122659729153346ddc4c4e457a5f96c Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Fri, 30 Sep 2022 12:04:55 -0400 Subject: [PATCH 12/35] defaultGateway -> parentGateway --- src/TeleportRouter.sol | 22 +++++++++++----------- src/test/TeleportRouter.t.sol | 24 ++++++++++++------------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 103e277..201f59d 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -38,7 +38,7 @@ contract TeleportRouter { mapping (bytes32 => uint256) public batches; // Pending DAI to flush per target domain EnumerableSet.Bytes32Set private allDomains; - address public defaultGateway; + address public parentGateway; uint80 public nonce; uint256 public fdust; // The minimum amount of DAI to be flushed per target domain (prevent spam) @@ -114,14 +114,14 @@ contract TeleportRouter { } /** - * @notice Allows auth to configure the router. The only supported operation is "defaultGateway", + * @notice Allows auth to configure the router. The only supported operation is "parentGateway", * which sets the fallback address if no specific domain is matched. - * @param what The name of the operation. Only "defaultGateway" is supported. + * @param what The name of the operation. Only "parentGateway" is supported. * @param data Set the fallback gateway or address(0) to disable the fallback. */ function file(bytes32 what, address data) external auth { - if (what == "defaultGateway") { - defaultGateway = data; + if (what == "parentGateway") { + parentGateway = data; dai.approve(data, type(uint256).max); } else { revert("TeleportRouter/file-unrecognized-param"); @@ -153,9 +153,9 @@ contract TeleportRouter { * @param teleportGUID The teleport GUID to register */ function registerMint(TeleportGUID calldata teleportGUID) external { - // We trust the defaultGateway gateway with any sourceDomain as a compromised defaultGateway implies compromised child + // We trust the parentGateway gateway with any sourceDomain as a compromised parentGateway implies compromised child // Otherwise we restrict passing messages only from the actual source domain - require(msg.sender == defaultGateway || msg.sender == gateways[teleportGUID.sourceDomain], "TeleportRouter/sender-not-gateway"); + require(msg.sender == parentGateway || msg.sender == gateways[teleportGUID.sourceDomain], "TeleportRouter/sender-not-gateway"); _registerMint(teleportGUID); } @@ -163,7 +163,7 @@ contract TeleportRouter { function _registerMint(TeleportGUID memory teleportGUID) internal { address gateway = gateways[teleportGUID.targetDomain]; // Use fallback if no gateway is configured for the target domain - if (gateway == address(0)) gateway = defaultGateway; + if (gateway == address(0)) gateway = parentGateway; require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); GatewayLike(gateway).registerMint(teleportGUID); } @@ -176,9 +176,9 @@ contract TeleportRouter { * @param amount The amount of DAI in the batch */ function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) external { - // We trust the defaultGateway gateway with any sourceDomain as a compromised defaultGateway implies compromised child + // We trust the parentGateway gateway with any sourceDomain as a compromised parentGateway implies compromised child // Otherwise we restrict passing messages only from the actual source domain - require(msg.sender == defaultGateway || msg.sender == gateways[sourceDomain], "TeleportRouter/sender-not-gateway"); + require(msg.sender == parentGateway || msg.sender == gateways[sourceDomain], "TeleportRouter/sender-not-gateway"); _settle(msg.sender, sourceDomain, targetDomain, amount); } @@ -186,7 +186,7 @@ contract TeleportRouter { function _settle(address from, bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) internal { address gateway = gateways[targetDomain]; // Use fallback if no gateway is configured for the target domain - if (gateway == address(0)) gateway = defaultGateway; + if (gateway == address(0)) gateway = parentGateway; require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); // Forward the DAI to settle to the gateway contract dai.transferFrom(from, gateway, amount); diff --git a/src/test/TeleportRouter.t.sol b/src/test/TeleportRouter.t.sol index e9c647a..f56f5e5 100644 --- a/src/test/TeleportRouter.t.sol +++ b/src/test/TeleportRouter.t.sol @@ -79,10 +79,10 @@ contract TeleportRouterTest is DSTest { function testFileFailsWhenNotAuthed() public { assertTrue(_tryFile("gateway", "dom", address(888))); - assertTrue(_tryFile("defaultGateway", address(888))); + assertTrue(_tryFile("parentGateway", address(888))); router.deny(address(this)); assertTrue(!_tryFile("gateway", "dom", address(888))); - assertTrue(!_tryFile("defaultGateway", address(888))); + assertTrue(!_tryFile("parentGateway", address(888))); } function testFileNewDomains() public { @@ -250,9 +250,9 @@ contract TeleportRouterTest is DSTest { } function testFile() public { - assertEq(router.defaultGateway(), address(0)); - assertTrue(_tryFile("defaultGateway", address(123))); - assertEq(router.defaultGateway(), address(123)); + assertEq(router.parentGateway(), address(0)); + assertTrue(_tryFile("parentGateway", address(123))); + assertEq(router.parentGateway(), address(123)); assertEq(router.fdust(), 0); assertTrue(_tryFile("fdust", 888)); @@ -326,7 +326,7 @@ contract TeleportRouterTest is DSTest { router.registerMint(guid); } - function testRegisterMintFromDefaultGateway() public { + function testRegisterMintFromparentGateway() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", targetDomain: "another-l2network", @@ -336,7 +336,7 @@ contract TeleportRouterTest is DSTest { nonce: 5, timestamp: uint48(block.timestamp) }); - router.file("defaultGateway", address(this)); + router.file("parentGateway", address(this)); router.file("gateway", "another-l2network", address(new GatewayMock())); router.registerMint(guid); @@ -368,8 +368,8 @@ contract TeleportRouterTest is DSTest { router.settle("l2network", "another-l2network", 100 ether); } - function testSettleFromDefaultGateway() public { - router.file("defaultGateway", address(this)); + function testSettleFromparentGateway() public { + router.file("parentGateway", address(this)); router.file("gateway", "another-l2network", address(new GatewayMock())); DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); @@ -384,7 +384,7 @@ contract TeleportRouterTest is DSTest { } function testInitiateTeleport() public { - router.file("defaultGateway", teleportJoin); + router.file("parentGateway", teleportJoin); DaiMock(dai).mint(address(this), 100_000 ether); DaiMock(dai).approve(address(router), 100_000 ether); @@ -402,7 +402,7 @@ contract TeleportRouterTest is DSTest { } function testFlush() public { - router.file("defaultGateway", teleportJoin); + router.file("parentGateway", teleportJoin); DaiMock(dai).mint(address(this), 100_000 ether); DaiMock(dai).approve(address(router), 100_000 ether); router.initiateTeleport("ethereum", address(123), 100_000 ether); @@ -419,7 +419,7 @@ contract TeleportRouterTest is DSTest { } function testFailFlushDust() public { - router.file("defaultGateway", teleportJoin); + router.file("parentGateway", teleportJoin); DaiMock(dai).mint(address(this), 100_000 ether); DaiMock(dai).approve(address(router), 100_000 ether); router.initiateTeleport("ethereum", address(123), 100_000 ether); From c7b48d521a9b7bd5e93992754e1eba9381d6771b Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Mon, 3 Oct 2022 09:28:22 -0400 Subject: [PATCH 13/35] address comments --- src/TeleportJoin.sol | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/TeleportJoin.sol b/src/TeleportJoin.sol index b1be0dd..d5841b9 100644 --- a/src/TeleportJoin.sol +++ b/src/TeleportJoin.sol @@ -36,7 +36,6 @@ interface DaiJoinLike { } interface TokenLike { - function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); function approve(address, uint256) external returns (bool); } @@ -44,11 +43,6 @@ interface FeesLike { function getFee(TeleportGUID calldata, uint256, int256, uint256, uint256) external view returns (uint256); } -interface GatewayLike { - function registerMint(TeleportGUID calldata teleportGUID) external; - function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) external; -} - // Primary control for extending Teleport credit contract TeleportJoin { mapping (address => uint256) public wards; // Auth @@ -63,7 +57,6 @@ contract TeleportJoin { VatLike immutable public vat; DaiJoinLike immutable public daiJoin; - TokenLike immutable public dai; bytes32 immutable public ilk; bytes32 immutable public domain; @@ -91,9 +84,8 @@ contract TeleportJoin { emit Rely(msg.sender); vat = VatLike(vat_); daiJoin = DaiJoinLike(daiJoin_); - dai = daiJoin.dai(); vat.hope(daiJoin_); - dai.approve(daiJoin_, type(uint256).max); + daiJoin.dai().approve(daiJoin_, type(uint256).max); ilk = ilk_; domain = domain_; } @@ -281,7 +273,7 @@ contract TeleportJoin { } /** - * @dev External function that repays debt with DAI pulled from the caller + * @dev External function that repays debt with DAI previously pushed to this contract (in general coming from the bridges) * @param sourceDomain domain where the DAI is coming from * @param targetDomain this domain * @param amount Amount of DAI that is being processed for repayment From e839df79dcd271b8a2f300d200bfd7dca89a6b90 Mon Sep 17 00:00:00 2001 From: Gonzalo Balabasquer Date: Wed, 5 Oct 2022 12:43:26 -0300 Subject: [PATCH 14/35] Replace parentGateway by gateways[parentDomain] and allow equal fdust for flush (#100) * Replace parentGateway by gateways[parentDomain] * Fix typo * More proper tests * >= fdust * Tests fixes --- src/TeleportRouter.sol | 36 +++------- src/test/TeleportRouter.t.sol | 124 +++++++++++++++++----------------- 2 files changed, 72 insertions(+), 88 deletions(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 201f59d..27936ce 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -38,17 +38,16 @@ contract TeleportRouter { mapping (bytes32 => uint256) public batches; // Pending DAI to flush per target domain EnumerableSet.Bytes32Set private allDomains; - address public parentGateway; uint80 public nonce; uint256 public fdust; // The minimum amount of DAI to be flushed per target domain (prevent spam) TokenLike immutable public dai; bytes32 immutable public domain; + bytes32 immutable public parentDomain; event Rely(address indexed usr); event Deny(address indexed usr); event File(bytes32 indexed what, bytes32 indexed domain, address data); - event File(bytes32 indexed what, address data); event File(bytes32 indexed what, uint256 data); event InitiateTeleport(TeleportGUID teleport); event Flush(bytes32 indexed targetDomain, uint256 dai); @@ -58,9 +57,10 @@ contract TeleportRouter { _; } - constructor(address dai_, bytes32 domain_) { + constructor(address dai_, bytes32 domain_, bytes32 parentDomain_) { dai = TokenLike(dai_); domain = domain_; + parentDomain = parentDomain_; wards[msg.sender] = 1; emit Rely(msg.sender); } @@ -112,22 +112,6 @@ contract TeleportRouter { } emit File(what, _domain, data); } - - /** - * @notice Allows auth to configure the router. The only supported operation is "parentGateway", - * which sets the fallback address if no specific domain is matched. - * @param what The name of the operation. Only "parentGateway" is supported. - * @param data Set the fallback gateway or address(0) to disable the fallback. - */ - function file(bytes32 what, address data) external auth { - if (what == "parentGateway") { - parentGateway = data; - dai.approve(data, type(uint256).max); - } else { - revert("TeleportRouter/file-unrecognized-param"); - } - emit File(what, data); - } function file(bytes32 what, uint256 data) external auth { if (what == "fdust") { @@ -153,9 +137,9 @@ contract TeleportRouter { * @param teleportGUID The teleport GUID to register */ function registerMint(TeleportGUID calldata teleportGUID) external { - // We trust the parentGateway gateway with any sourceDomain as a compromised parentGateway implies compromised child + // We trust the parent gateway with any sourceDomain as a compromised parent gateway implies compromised child // Otherwise we restrict passing messages only from the actual source domain - require(msg.sender == parentGateway || msg.sender == gateways[teleportGUID.sourceDomain], "TeleportRouter/sender-not-gateway"); + require(msg.sender == gateways[parentDomain] || msg.sender == gateways[teleportGUID.sourceDomain], "TeleportRouter/sender-not-gateway"); _registerMint(teleportGUID); } @@ -163,7 +147,7 @@ contract TeleportRouter { function _registerMint(TeleportGUID memory teleportGUID) internal { address gateway = gateways[teleportGUID.targetDomain]; // Use fallback if no gateway is configured for the target domain - if (gateway == address(0)) gateway = parentGateway; + if (gateway == address(0)) gateway = gateways[parentDomain]; require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); GatewayLike(gateway).registerMint(teleportGUID); } @@ -176,9 +160,9 @@ contract TeleportRouter { * @param amount The amount of DAI in the batch */ function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) external { - // We trust the parentGateway gateway with any sourceDomain as a compromised parentGateway implies compromised child + // We trust the parent gateway with any sourceDomain as a compromised parent gateway implies compromised child // Otherwise we restrict passing messages only from the actual source domain - require(msg.sender == parentGateway || msg.sender == gateways[sourceDomain], "TeleportRouter/sender-not-gateway"); + require(msg.sender == gateways[parentDomain] || msg.sender == gateways[sourceDomain], "TeleportRouter/sender-not-gateway"); _settle(msg.sender, sourceDomain, targetDomain, amount); } @@ -186,7 +170,7 @@ contract TeleportRouter { function _settle(address from, bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) internal { address gateway = gateways[targetDomain]; // Use fallback if no gateway is configured for the target domain - if (gateway == address(0)) gateway = parentGateway; + if (gateway == address(0)) gateway = gateways[parentDomain]; require(gateway != address(0), "TeleportRouter/unsupported-target-domain"); // Forward the DAI to settle to the gateway contract dai.transferFrom(from, gateway, amount); @@ -276,7 +260,7 @@ contract TeleportRouter { **/ function flush(bytes32 targetDomain) external { uint256 daiToFlush = batches[targetDomain]; - require(daiToFlush > fdust, "DomainGuest/flush-dust"); + require(daiToFlush >= fdust, "DomainGuest/flush-dust"); batches[targetDomain] = 0; diff --git a/src/test/TeleportRouter.t.sol b/src/test/TeleportRouter.t.sol index f56f5e5..e5690f6 100644 --- a/src/test/TeleportRouter.t.sol +++ b/src/test/TeleportRouter.t.sol @@ -28,14 +28,15 @@ contract TeleportRouterTest is DSTest { TeleportRouter internal router; address internal dai; address internal teleportJoin; - bytes32 constant internal l1Domain = "ethereum"; + bytes32 constant internal domain = "rollup"; + bytes32 constant internal parentDomain = "ethereum"; uint256 internal constant WAD = 10**18; function setUp() public { dai = address(new DaiMock()); teleportJoin = address(new GatewayMock()); - router = new TeleportRouter(dai, l1Domain); + router = new TeleportRouter(dai, domain, parentDomain); } function _tryRely(address usr) internal returns (bool ok) { @@ -46,8 +47,8 @@ contract TeleportRouterTest is DSTest { (ok,) = address(router).call(abi.encodeWithSignature("deny(address)", usr)); } - function _tryFile(bytes32 what, bytes32 domain, address data) internal returns (bool ok) { - (ok,) = address(router).call(abi.encodeWithSignature("file(bytes32,bytes32,address)", what, domain, data)); + function _tryFile(bytes32 what, bytes32 _domain, address data) internal returns (bool ok) { + (ok,) = address(router).call(abi.encodeWithSignature("file(bytes32,bytes32,address)", what, _domain, data)); } function _tryFile(bytes32 what, address data) internal returns (bool ok) { @@ -60,7 +61,8 @@ contract TeleportRouterTest is DSTest { function testConstructor() public { assertEq(address(router.dai()), dai); - assertEq(router.domain(), l1Domain); + assertEq(router.domain(), domain); + assertEq(router.parentDomain(), parentDomain); assertEq(router.wards(address(this)), 1); } @@ -77,14 +79,6 @@ contract TeleportRouterTest is DSTest { assertTrue(!_tryDeny(address(456))); } - function testFileFailsWhenNotAuthed() public { - assertTrue(_tryFile("gateway", "dom", address(888))); - assertTrue(_tryFile("parentGateway", address(888))); - router.deny(address(this)); - assertTrue(!_tryFile("gateway", "dom", address(888))); - assertTrue(!_tryFile("parentGateway", address(888))); - } - function testFileNewDomains() public { bytes32 domain1 = "newdom1"; address gateway1 = address(111); @@ -110,34 +104,35 @@ contract TeleportRouterTest is DSTest { } function testFileNewGatewayForExistingDomain() public { - bytes32 domain = "dom"; + bytes32 domain1 = "dom"; address gateway1 = address(111); - assertTrue(_tryFile("gateway", domain, gateway1)); - assertEq(router.gateways(domain), gateway1); + assertTrue(_tryFile("gateway", domain1, gateway1)); + assertEq(router.gateways(domain1), gateway1); assertEq(router.numDomains(), 1); - assertEq(router.domainAt(0), domain); + assertEq(router.domainAt(0), domain1); address gateway2 = address(222); - assertTrue(_tryFile("gateway", domain, gateway2)); + assertTrue(_tryFile("gateway", domain1, gateway2)); - assertEq(router.gateways(domain), gateway2); + assertEq(router.gateways(domain1), gateway2); assertEq(router.numDomains(), 1); - assertEq(router.domainAt(0), domain); + assertEq(router.domainAt(0), domain1); } function testFileRemoveLastDomain() public { - bytes32 domain = "dom"; + bytes32 domain1 = "dom"; address gateway = address(111); - assertTrue(_tryFile("gateway", domain, gateway)); - assertEq(router.gateways(domain), gateway); + assertTrue(_tryFile("gateway", domain1, gateway)); + assertEq(router.gateways(domain1), gateway); assertEq(router.numDomains(), 1); - assertEq(router.domainAt(0), domain); + assertEq(router.domainAt(0), domain1); - // Remove last domain - assertTrue(_tryFile("gateway", domain, address(0))); + // Remove last domain1 + assertTrue(_tryFile("gateway", domain1, address(0))); - assertEq(router.gateways(domain), address(0)); - assertTrue(!router.hasDomain(domain)); + assertEq(router.gateways(domain1), address(0)); + assertTrue(!router.hasDomain(domain1)); + assertEq(router.numDomains(), 0); } @@ -250,10 +245,6 @@ contract TeleportRouterTest is DSTest { } function testFile() public { - assertEq(router.parentGateway(), address(0)); - assertTrue(_tryFile("parentGateway", address(123))); - assertEq(router.parentGateway(), address(123)); - assertEq(router.fdust(), 0); assertTrue(_tryFile("fdust", 888)); assertEq(router.fdust(), 888); @@ -264,10 +255,16 @@ contract TeleportRouterTest is DSTest { assertTrue(!_tryFile("meh", address(888))); } + function testFileFailsWhenNotAuthed() public { + router.deny(address(this)); + assertTrue(!_tryFile("gateway", "dom", address(888))); + assertTrue(!_tryFile("fdust", 1)); + } + function testFailRegisterMintFromNotGateway() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", - targetDomain: l1Domain, + targetDomain: domain, receiver: addressToBytes32(address(123)), operator: addressToBytes32(address(234)), amount: 250_000 ether, @@ -279,10 +276,10 @@ contract TeleportRouterTest is DSTest { router.registerMint(guid); } - function testRegisterMintTargetingL1() public { + function testRegisterMintTargetingActualDomain() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", - targetDomain: l1Domain, + targetDomain: domain, receiver: addressToBytes32(address(123)), operator: addressToBytes32(address(234)), amount: 250_000 ether, @@ -290,12 +287,12 @@ contract TeleportRouterTest is DSTest { timestamp: uint48(block.timestamp) }); router.file("gateway", "l2network", address(this)); - router.file("gateway", l1Domain, teleportJoin); + router.file("gateway", domain, teleportJoin); router.registerMint(guid); } - function testRegisterMintTargetingL2() public { + function testRegisterMintTargetingSubDomain() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", targetDomain: "another-l2network", @@ -326,7 +323,7 @@ contract TeleportRouterTest is DSTest { router.registerMint(guid); } - function testRegisterMintFromparentGateway() public { + function testRegisterMintFromParentGateway() public { TeleportGUID memory guid = TeleportGUID({ sourceDomain: "l2network", targetDomain: "another-l2network", @@ -336,7 +333,7 @@ contract TeleportRouterTest is DSTest { nonce: 5, timestamp: uint48(block.timestamp) }); - router.file("parentGateway", address(this)); + router.file("gateway", parentDomain, address(this)); router.file("gateway", "another-l2network", address(new GatewayMock())); router.registerMint(guid); @@ -347,19 +344,19 @@ contract TeleportRouterTest is DSTest { DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); - router.settle("l2network", l1Domain, 100 ether); + router.settle("l2network", domain, 100 ether); } - function testSettleTargetingL1() public { + function testSettleTargetingActualDomain() public { router.file("gateway", "l2network", address(this)); - router.file("gateway", l1Domain, teleportJoin); + router.file("gateway", domain, teleportJoin); DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); - router.settle("l2network", l1Domain, 100 ether); + router.settle("l2network", domain, 100 ether); } - function testSettleTargetingL2() public { + function testSettleTargetingSubDomain() public { router.file("gateway", "l2network", address(this)); router.file("gateway", "another-l2network", address(new GatewayMock())); DaiMock(dai).mint(address(this), 100 ether); @@ -368,8 +365,8 @@ contract TeleportRouterTest is DSTest { router.settle("l2network", "another-l2network", 100 ether); } - function testSettleFromparentGateway() public { - router.file("parentGateway", address(this)); + function testSettleFromParentGateway() public { + router.file("gateway", parentDomain, address(this)); router.file("gateway", "another-l2network", address(new GatewayMock())); DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); @@ -384,51 +381,54 @@ contract TeleportRouterTest is DSTest { } function testInitiateTeleport() public { - router.file("parentGateway", teleportJoin); + address parentGateway = address(new GatewayMock()); + router.file("gateway", parentDomain, parentGateway); DaiMock(dai).mint(address(this), 100_000 ether); DaiMock(dai).approve(address(router), 100_000 ether); assertEq(DaiMock(dai).balanceOf(address(this)), 100_000 ether); assertEq(DaiMock(dai).balanceOf(address(router)), 0); - assertEq(router.batches("ethereum"), 0); + assertEq(router.batches(parentDomain), 0); assertEq(router.nonce(), 0); - router.initiateTeleport("ethereum", address(123), 100_000 ether); + router.initiateTeleport(parentDomain, address(123), 100_000 ether); assertEq(DaiMock(dai).balanceOf(address(this)), 0); assertEq(DaiMock(dai).balanceOf(address(router)), 100_000 ether); - assertEq(router.batches("ethereum"), 100_000 ether); + assertEq(router.batches(parentDomain), 100_000 ether); assertEq(router.nonce(), 1); } function testFlush() public { - router.file("parentGateway", teleportJoin); + address parentGateway = address(new GatewayMock()); + router.file("gateway", parentDomain, parentGateway); DaiMock(dai).mint(address(this), 100_000 ether); DaiMock(dai).approve(address(router), 100_000 ether); - router.initiateTeleport("ethereum", address(123), 100_000 ether); + router.initiateTeleport(parentDomain, address(123), 100_000 ether); - assertEq(router.batches("ethereum"), 100_000 ether); + assertEq(router.batches(parentDomain), 100_000 ether); assertEq(DaiMock(dai).balanceOf(address(router)), 100_000 ether); - assertEq(DaiMock(dai).balanceOf(teleportJoin), 0); + assertEq(DaiMock(dai).balanceOf(parentGateway), 0); - router.flush("ethereum"); + router.flush(parentDomain); - assertEq(router.batches("ethereum"), 0); + assertEq(router.batches(parentDomain), 0); assertEq(DaiMock(dai).balanceOf(address(router)), 0); - assertEq(DaiMock(dai).balanceOf(teleportJoin), 100_000 ether); + assertEq(DaiMock(dai).balanceOf(parentGateway), 100_000 ether); } function testFailFlushDust() public { - router.file("parentGateway", teleportJoin); + address parentGateway = address(new GatewayMock()); + router.file("gateway", parentDomain, parentGateway); DaiMock(dai).mint(address(this), 100_000 ether); DaiMock(dai).approve(address(router), 100_000 ether); - router.initiateTeleport("ethereum", address(123), 100_000 ether); + router.initiateTeleport(parentDomain, address(123), 100_000 ether); - assertEq(router.batches("ethereum"), 100_000 ether); + assertEq(router.batches(parentDomain), 100_000 ether); assertEq(DaiMock(dai).balanceOf(address(router)), 100_000 ether); - assertEq(DaiMock(dai).balanceOf(teleportJoin), 0); + assertEq(DaiMock(dai).balanceOf(parentGateway), 0); router.file("fdust", 200_000 ether); - router.flush("ethereum"); + router.flush(parentDomain); } } From 40fe5dd7a3268f169ad945b1d6daccbff5b25c5e Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Wed, 5 Oct 2022 14:05:42 -0400 Subject: [PATCH 15/35] fix reverts --- src/TeleportRouter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 27936ce..8782c65 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -244,7 +244,7 @@ contract TeleportRouter { }); batches[targetDomain] += amount; - require(dai.transferFrom(msg.sender, address(this), amount), "DomainHost/transfer-failed"); + require(dai.transferFrom(msg.sender, address(this), amount), "TeleportRouter/transfer-failed"); // Initiate the censorship-resistant slow-path _registerMint(teleport); @@ -260,7 +260,7 @@ contract TeleportRouter { **/ function flush(bytes32 targetDomain) external { uint256 daiToFlush = batches[targetDomain]; - require(daiToFlush >= fdust, "DomainGuest/flush-dust"); + require(daiToFlush >= fdust, "TeleportRouter/flush-dust"); batches[targetDomain] = 0; From fbe33d43635038e59f8a24765427d01bec4a86bc Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Wed, 5 Oct 2022 14:13:15 -0400 Subject: [PATCH 16/35] Update src/TeleportRouter.sol Co-authored-by: olivdb --- src/TeleportRouter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 8782c65..be1203b 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -79,7 +79,7 @@ contract TeleportRouter { * @notice Allows auth to configure the router. The only supported operation is "gateway", * which allows adding, replacing or removing a gateway contract for a given domain. The router forwards `settle()` * and `registerMint()` calls to the gateway contract installed for a given domain. Gateway contracts must therefore - * conform to the GatewayLike interface. Examples of valid gateways include TeleportJoin (for the L1 domain) + * conform to the GatewayLike interface. Examples of valid gateways include TeleportJoin (for the domain of the TeleportRouter instance), * and L1 bridge contracts (for L2 domains). * @dev In addition to updating the mapping `gateways` which maps GatewayLike contracts to domain names this method * also maintains the enumerable set `allDomains`. From f06f3e82d102efeb755d25483fbfe1ddafcae00c Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Wed, 5 Oct 2022 14:15:34 -0400 Subject: [PATCH 17/35] Update src/TeleportRouter.sol Co-authored-by: olivdb --- src/TeleportRouter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index be1203b..90371f3 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -80,7 +80,7 @@ contract TeleportRouter { * which allows adding, replacing or removing a gateway contract for a given domain. The router forwards `settle()` * and `registerMint()` calls to the gateway contract installed for a given domain. Gateway contracts must therefore * conform to the GatewayLike interface. Examples of valid gateways include TeleportJoin (for the domain of the TeleportRouter instance), - * and L1 bridge contracts (for L2 domains). + * , DomainHost bridges (for the domains that are children to TeleportRouter's domain) and a DomainGuest bridge (for the domain that is parent to TeleportRouter's domain). * @dev In addition to updating the mapping `gateways` which maps GatewayLike contracts to domain names this method * also maintains the enumerable set `allDomains`. * @param what The name of the operation. Only "gateway" is supported. From 485897c733684e9162389fafdcd61758b3a2ce59 Mon Sep 17 00:00:00 2001 From: Gonzalo Balabasquer Date: Wed, 5 Oct 2022 16:38:46 -0300 Subject: [PATCH 18/35] Certora: update join specs for new version + add new files to gitignore --- .gitignore | 2 + certora/TeleportJoin.spec | 100 ++++++++++++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 2f66313..98c5c35 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ .last_confs/ *.zip resource_errors.json +.zip-output-url.txt +certora_debug_log.txt diff --git a/certora/TeleportJoin.spec b/certora/TeleportJoin.spec index 65ad8ce..3f3e4b8 100644 --- a/certora/TeleportJoin.spec +++ b/certora/TeleportJoin.spec @@ -265,6 +265,62 @@ definition operatorFeeAmt(bool canGenerate, uint256 operatorFee) : 0; +// Verify that registerMint behaves correctly +rule registerMint( + join.TeleportGUID guid + ) { + env e; + + bytes32 hashGUID = aux.getGUIDHash(guid); + + bool blessedBefore; + uint248 pendingBefore; + blessedBefore, pendingBefore = teleports(hashGUID); + + registerMint(e, guid); + + bool blessedAfter; + uint248 pendingAfter; + blessedAfter, pendingAfter = teleports(hashGUID); + + assert(blessedBefore == false, "blessed before call should be false"); + assert(blessedAfter == true, "blessed after call should be true"); + assert(pendingAfter == guid.amount, "pending has not acted as expected"); +} + +// Verify revert rules on registerMint +rule registerMint_revert( + join.TeleportGUID guid + ) { + env e; + + uint256 ward = wards(e.msg.sender); + + bytes32 hashGUID = aux.getGUIDHash(guid); + + bytes32 domain = domain(); + + bool vatLive = vat.live() == 1; + uint256 line = vatLive ? line(guid.sourceDomain): 0; + int256 debt = debt(guid.sourceDomain); + + bool blessed; + uint248 pending; + blessed, pending = teleports(hashGUID); + + registerMint@withrevert(e, guid); + + bool revert1 = e.msg.value > 0; + bool revert2 = ward != 1; + bool revert3 = blessed; + + assert(revert1 => lastReverted, "revert1 failed"); + assert(revert2 => lastReverted, "revert2 failed"); + assert(revert3 => lastReverted, "revert3 failed"); + + assert(lastReverted => revert1 || revert2 || revert3, "Revert rules are not covering all the cases"); +} + // Verify that requestMint behaves correctly rule requestMint( join.TeleportGUID guid, @@ -665,7 +721,7 @@ rule mintPending_revert( revert22 || revert23 || revert24, "Revert rules are not covering all the cases"); } -rule settle(bytes32 sourceDomain, uint256 batchedDaiToFlush) { +rule settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) { env e; bool vatLive = vat.live() == 1; @@ -679,9 +735,9 @@ rule settle(bytes32 sourceDomain, uint256 batchedDaiToFlush) { uint256 vatDaiJoinBefore = vat.dai(currentContract); - uint256 amtToPayBack = batchedDaiToFlush <= artBefore ? batchedDaiToFlush : artBefore; + uint256 amtToPayBack = amount <= artBefore ? amount : artBefore; - settle(e, sourceDomain, batchedDaiToFlush); + settle(e, sourceDomain, targetDomain, amount); int256 debtAfter = debt(sourceDomain); @@ -693,24 +749,26 @@ rule settle(bytes32 sourceDomain, uint256 batchedDaiToFlush) { uint256 vatDaiJoinAfter = vat.dai(currentContract); - assert(to_mathint(debtAfter) == to_mathint(debtBefore) - to_mathint(batchedDaiToFlush), "debt has not decreased as expected"); + assert(to_mathint(debtAfter) == to_mathint(debtBefore) - to_mathint(amount), "debt has not decreased as expected"); assert(vatLive => inkAfter == inkBefore - amtToPayBack, "ink has not decreased as expected"); assert(vatLive => artAfter == artBefore - amtToPayBack, "art has not decreased as expected"); assert(!vatLive => inkAfter == inkBefore, "ink has not stayed the same as expected"); assert(!vatLive => artAfter == artBefore, "art has not stayed the same as expected"); assert(vatLive => cureAfter == (artBefore - amtToPayBack) * RAY(), "cure has not been updated as expected"); assert(!vatLive => cureAfter == cureBefore, "cure has not stayed the same as expected"); - assert(vatLive => vatDaiJoinAfter == vatDaiJoinBefore + (batchedDaiToFlush - amtToPayBack) * RAY(), "join vat dai has not increased as expected 1"); - assert(!vatLive => vatDaiJoinAfter == vatDaiJoinBefore + batchedDaiToFlush * RAY(), "join vat dai has not increased as expected 2"); + assert(vatLive => vatDaiJoinAfter == vatDaiJoinBefore + (amount - amtToPayBack) * RAY(), "join vat dai has not increased as expected 1"); + assert(!vatLive => vatDaiJoinAfter == vatDaiJoinBefore + amount * RAY(), "join vat dai has not increased as expected 2"); } -rule settle_revert(bytes32 sourceDomain, uint256 batchedDaiToFlush) { +rule settle_revert(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) { env e; require(vat() == vat); require(daiJoin.vat() == vat); require(daiJoin.dai() == dai); + bytes32 domain = domain(); + bool vatLive = vat.live() == 1; int256 debt = debt(sourceDomain); @@ -727,21 +785,22 @@ rule settle_revert(bytes32 sourceDomain, uint256 batchedDaiToFlush) { uint256 daiBalJoin = dai.balanceOf(currentContract); uint256 daiAllJoinDaiJoin = dai.allowance(currentContract, daiJoin); - uint256 amtToPayBack = batchedDaiToFlush <= art ? batchedDaiToFlush : art; + uint256 amtToPayBack = amount <= art ? amount : art; - settle@withrevert(e, sourceDomain, batchedDaiToFlush); + settle@withrevert(e, sourceDomain, targetDomain, amount); bool revert1 = e.msg.value > 0; - bool revert2 = batchedDaiToFlush > max_int256(); - bool revert3 = batchedDaiToFlush * RAY() > max_uint256; - bool revert4 = batchedDaiToFlush * RAY() > vatDaiDaiJoin; - bool revert5 = vatDaiJoin + batchedDaiToFlush * RAY() > max_uint256; - bool revert6 = daiBalJoin < batchedDaiToFlush; - bool revert7 = daiAllJoinDaiJoin < batchedDaiToFlush; - bool revert8 = vatLive && amtToPayBack > 0 && -1 * to_mathint(amtToPayBack) * RAY() < min_int256(); - bool revert9 = vatLive && amtToPayBack > ink; - bool revert10 = vatLive && vatGemJoin + amtToPayBack > max_uint256; - bool revert11 = to_mathint(debt) - to_mathint(batchedDaiToFlush) < min_int256(); + bool revert2 = targetDomain != domain; + bool revert3 = amount > max_int256(); + bool revert4 = amount * RAY() > max_uint256; + bool revert5 = amount * RAY() > vatDaiDaiJoin; + bool revert6 = vatDaiJoin + amount * RAY() > max_uint256; + bool revert7 = daiBalJoin < amount; + bool revert8 = daiAllJoinDaiJoin < amount; + bool revert9 = vatLive && amtToPayBack > 0 && -1 * to_mathint(amtToPayBack) * RAY() < min_int256(); + bool revert10 = vatLive && amtToPayBack > ink; + bool revert11 = vatLive && vatGemJoin + amtToPayBack > max_uint256; + bool revert12 = to_mathint(debt) - to_mathint(amount) < min_int256(); assert(revert1 => lastReverted, "revert1 failed"); assert(revert2 => lastReverted, "revert2 failed"); @@ -754,9 +813,10 @@ rule settle_revert(bytes32 sourceDomain, uint256 batchedDaiToFlush) { assert(revert9 => lastReverted, "revert9 failed"); assert(revert10 => lastReverted, "revert10 failed"); assert(revert11 => lastReverted, "revert11 failed"); + assert(revert12 => lastReverted, "revert12 failed"); assert(lastReverted => revert1 || revert2 || revert3 || revert4 || revert5 || revert6 || revert7 || revert8 || revert9 || - revert10 || revert11, "Revert rules are not covering all the cases"); + revert10 || revert11 || revert12, "Revert rules are not covering all the cases"); } From 181bcdecd806d63ead13df875700d64795a192cc Mon Sep 17 00:00:00 2001 From: Gonzalo Balabasquer Date: Wed, 5 Oct 2022 16:54:46 -0300 Subject: [PATCH 19/35] Fix comment --- src/TeleportRouter.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 90371f3..64ea321 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -80,7 +80,8 @@ contract TeleportRouter { * which allows adding, replacing or removing a gateway contract for a given domain. The router forwards `settle()` * and `registerMint()` calls to the gateway contract installed for a given domain. Gateway contracts must therefore * conform to the GatewayLike interface. Examples of valid gateways include TeleportJoin (for the domain of the TeleportRouter instance), - * , DomainHost bridges (for the domains that are children to TeleportRouter's domain) and a DomainGuest bridge (for the domain that is parent to TeleportRouter's domain). + * DomainHost bridges (for the domains that are children to TeleportRouter's domain) + * and a DomainGuest bridge (for the domain that is parent to TeleportRouter's domain). * @dev In addition to updating the mapping `gateways` which maps GatewayLike contracts to domain names this method * also maintains the enumerable set `allDomains`. * @param what The name of the operation. Only "gateway" is supported. From 759ec185cdc91812120c057ae14fe81c8d3ce3f4 Mon Sep 17 00:00:00 2001 From: Gonzalo Balabasquer Date: Thu, 6 Oct 2022 09:50:23 -0300 Subject: [PATCH 20/35] Certora: update router specs for new version --- Makefile | 2 +- certora/Auxiliar.sol | 4 + certora/GatewayMock.sol | 23 +++ certora/TeleportJoinMock.sol | 7 - certora/TeleportRouter.spec | 337 ++++++++++++++++++++++++++++------- 5 files changed, 302 insertions(+), 71 deletions(-) create mode 100644 certora/GatewayMock.sol diff --git a/Makefile b/Makefile index 39a1111..3d20875 100644 --- a/Makefile +++ b/Makefile @@ -4,5 +4,5 @@ test :; ./test.sh $(match) $(runs) cov :; DAPP_BUILD_OPTIMIZE=1 DAPP_BUILD_OPTIMIZE_RUNS=200 dapp --use solc:0.8.15 test -v --coverage --cov-match "src\/Teleport" certora-fee :; certoraRun --solc ~/.solc-select/artifacts/solc-0.8.15 --solc_args "['--optimize','--optimize-runs','200']" --rule_sanity basic src/TeleportConstantFee.sol --verify TeleportConstantFee:certora/TeleportConstantFee.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output certora-join :; certoraRun --solc ~/.solc-select/artifacts/solc-0.8.15 --solc_args "['--optimize','--optimize-runs','200']" --rule_sanity basic src/TeleportJoin.sol certora/FeesMock.sol certora/Auxiliar.sol src/test/mocks/VatMock.sol src/test/mocks/DaiMock.sol src/test/mocks/DaiJoinMock.sol --link TeleportJoin:vat=VatMock TeleportJoin:daiJoin=DaiJoinMock DaiJoinMock:vat=VatMock DaiJoinMock:dai=DaiMock --verify TeleportJoin:certora/TeleportJoin.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output -certora-router :; certoraRun --solc ~/.solc-select/artifacts/solc-0.8.15 --solc_args "['--optimize','--optimize-runs','200']" --rule_sanity basic src/TeleportRouter.sol certora/TeleportJoinMock.sol src/test/mocks/DaiMock.sol --link TeleportRouter:dai=DaiMock --verify TeleportRouter:certora/TeleportRouter.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output +certora-router :; certoraRun --solc ~/.solc-select/artifacts/solc-0.8.15 --solc_args "['--optimize','--optimize-runs','200']" --rule_sanity basic src/TeleportRouter.sol certora/GatewayMock.sol src/test/mocks/DaiMock.sol certora/Auxiliar.sol --link TeleportRouter:dai=DaiMock --verify TeleportRouter:certora/TeleportRouter.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output certora-oracle :; certoraRun --solc ~/.solc-select/artifacts/solc-0.8.15 --solc_args "['--optimize','--optimize-runs','200']" --rule_sanity basic src/TeleportOracleAuth.sol certora/TeleportJoinMock.sol certora/Auxiliar.sol --link TeleportOracleAuth:teleportJoin=TeleportJoinMock Auxiliar:oracle=TeleportOracleAuth --verify TeleportOracleAuth:certora/TeleportOracleAuth.spec --loop_iter 10 --optimistic_loop $(if $(rule),--rule $(rule),) --multi_assert_check --short_output diff --git a/certora/Auxiliar.sol b/certora/Auxiliar.sol index d67cb45..78a91f1 100644 --- a/certora/Auxiliar.sol +++ b/certora/Auxiliar.sol @@ -32,6 +32,10 @@ contract Auxiliar { )); } + function addressToBytes32(address addr) external pure returns (bytes32) { + return bytes32(uint256(uint160(addr))); + } + function bytes32ToAddress(bytes32 addr) external pure returns (address) { return address(uint160(uint256(addr))); } diff --git a/certora/GatewayMock.sol b/certora/GatewayMock.sol new file mode 100644 index 0000000..a1eb75b --- /dev/null +++ b/certora/GatewayMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity 0.8.15; + +import "../src/TeleportGUID.sol"; + +contract GatewayMock { + TeleportGUID public teleportGUID; + bytes32 public sourceDomain; + bytes32 public targetDomain; + uint256 public amount; + + function registerMint( + TeleportGUID memory teleportGUID_ + ) external { + teleportGUID = teleportGUID_; + } + + function settle(bytes32 sourceDomain_, bytes32 targetDomain_, uint256 amount_) external { + sourceDomain = sourceDomain_; + targetDomain = targetDomain_; + amount = amount_; + } +} diff --git a/certora/TeleportJoinMock.sol b/certora/TeleportJoinMock.sol index da4d5ad..c2f62a5 100644 --- a/certora/TeleportJoinMock.sol +++ b/certora/TeleportJoinMock.sol @@ -9,8 +9,6 @@ contract TeleportJoinMock { uint256 public operatorFee; uint256 public postFeeAmount; uint256 public totalFee; - bytes32 public sourceDomain; - uint256 public batchedDaiToFlush; function requestMint( TeleportGUID memory teleportGUID_, @@ -23,9 +21,4 @@ contract TeleportJoinMock { postFeeAmount_ = postFeeAmount; totalFee_ = totalFee; } - - function settle(bytes32 sourceDomain_, uint256 batchedDaiToFlush_) external { - sourceDomain = sourceDomain_; - batchedDaiToFlush = batchedDaiToFlush_; - } } diff --git a/certora/TeleportRouter.spec b/certora/TeleportRouter.spec index eb5a4ba..b0672de 100644 --- a/certora/TeleportRouter.spec +++ b/certora/TeleportRouter.spec @@ -2,30 +2,36 @@ using TeleportRouter as router using DaiMock as dai -using TeleportJoinMock as join +using GatewayMock as gateway +using Auxiliar as aux methods { + batches(bytes32) returns (uint256) envfree + domain() returns (bytes32) envfree domainAt(uint256) returns (bytes32) envfree - domains(address) returns (bytes32) envfree gateways(bytes32) returns (address) envfree hasDomain(bytes32) returns (bool) envfree + fdust() returns (uint256) envfree numDomains() returns (uint256) envfree - requestMint(join.TeleportGUID, uint256, uint256) returns (uint256, uint256) => DISPATCHER(true) - settle(bytes32, uint256) => DISPATCHER(true) + nonce() returns (uint80) envfree + parentDomain() returns (bytes32) envfree + registerMint(gateway.TeleportGUID) => DISPATCHER(true) + settle(bytes32, bytes32, uint256) => DISPATCHER(true) wards(address) returns (uint256) envfree + aux.addressToBytes32(address) returns (bytes32) envfree + aux.bytes32ToAddress(bytes32) returns (address) envfree dai.allowance(address, address) returns (uint256) envfree dai.balanceOf(address) returns (uint256) envfree - join.teleportGUID() returns(bytes32, bytes32, bytes32, bytes32, uint128, uint80, uint48) envfree - join.batchedDaiToFlush() returns (uint256) envfree - join.maxFeePercentage() returns (uint256) envfree - join.operatorFee() returns (uint256) envfree - join.postFeeAmount() returns (uint256) envfree - join.sourceDomain() returns (bytes32) envfree - join.totalFee() returns (uint256) envfree + gateway.teleportGUID() returns(bytes32, bytes32, bytes32, bytes32, uint128, uint80, uint48) envfree + gateway.sourceDomain() returns(bytes32) envfree + gateway.targetDomain() returns(bytes32) envfree + gateway.amount() returns(uint256) envfree } definition RAY() returns uint256 = 10^27; +definition max_uint48() returns mathint = 2^48 - 1; +definition max_uint80() returns mathint = 2^80 - 1; definition min_int256() returns mathint = -1 * 2^255; definition max_int256() returns mathint = 2^255 - 1; @@ -132,7 +138,7 @@ rule file_domain_address(bytes32 what, bytes32 domain, address data) { ); assert( !dataIsEmpty - => domains(data) == domain, "file did not set domains(gateway) as expected" + => dai.allowance(currentContract, data) == max_uint256, "file did not set max allowance as expected" ); assert( gatewayWasEmpty && !hasDomainBefore && !dataIsEmpty && numDomainsBefore < max_uint256 @@ -143,10 +149,10 @@ rule file_domain_address(bytes32 what, bytes32 domain, address data) { !lastReverted && gatewayWasEmpty && !dataIsEmpty => domainAt == domain, "file did not modify allDomains as expected" ); - assert( - !gatewayWasEmpty && gatewayBefore != data - => domains(gatewayBefore) == 0x0000000000000000000000000000000000000000000000000000000000000000, "file did not set domains(gateway) as expected 2" - ); + // assert( + // !gatewayWasEmpty && gatewayBefore != data + // => dai.allowance(currentContract, gatewayBefore) == 0, "file did not clear allowance as expected" + // ); assert( !gatewayWasEmpty && hasDomainBefore && dataIsEmpty => numDomainsAfter == numDomainsBefore - 1, "file did not decrease allDomains length as expected" @@ -160,8 +166,7 @@ rule file_domain_address_revert(bytes32 what, bytes32 domain, address data) { bool whatIsGateway = what == 0x6761746577617900000000000000000000000000000000000000000000000000; bool dataIsEmpty = data == 0x0000000000000000000000000000000000000000; uint256 ward = wards(e.msg.sender); - address gateway = gateways(domain); - bool gatewayWasEmpty = gateway == 0x0000000000000000000000000000000000000000; + bool gatewayWasEmpty = gateways(domain) == 0x0000000000000000000000000000000000000000; uint256 numDomains = numDomains(); bool hasDomain = hasDomain(domain); uint256 pos = indexesGhost(domain); @@ -186,19 +191,42 @@ rule file_domain_address_revert(bytes32 what, bytes32 domain, address data) { revert4 || revert5, "Revert rules are not covering all the cases"); } -// Verify that requestMint behaves correctly -rule requestMint( - router.TeleportGUID guid, - uint256 maxFeePercentage, - uint256 operatorFee - ) { +// Verify that fdust behaves correctly on file +rule file_uint256(bytes32 what, uint256 data) { + env e; + + file(e, what, data); + + assert(fdust() == data, "file did not set fdust as expected"); +} + + +// Verify revert rules on file +rule file_uint256_revert(bytes32 what, uint256 data) { + env e; + + uint256 ward = wards(e.msg.sender); + + file@withrevert(e, what, data); + + bool revert1 = e.msg.value > 0; + bool revert2 = ward != 1; + bool revert3 = what != 0x6664757374000000000000000000000000000000000000000000000000000000; + + assert(revert1 => lastReverted, "revert1 failed"); + assert(revert2 => lastReverted, "revert2 failed"); + assert(revert3 => lastReverted, "revert3 failed"); + + assert(lastReverted => revert1 || revert2 || revert3, "Revert rules are not covering all the cases"); +} + +// Verify that registerMint behaves correctly +rule registerMint(router.TeleportGUID guid) { env e; - require(gateways(guid.targetDomain) == join); + require(gateways(guid.targetDomain) == gateway); - uint256 postFeeAmount; - uint256 totalFee; - postFeeAmount, totalFee = requestMint(e, guid, maxFeePercentage, operatorFee); + registerMint(e, guid); bytes32 sourceDomain; bytes32 targetDomain; @@ -207,7 +235,7 @@ rule requestMint( uint128 amount; uint80 nonce; uint48 timestamp; - sourceDomain, targetDomain, receiver, operator, amount, nonce, timestamp = join.teleportGUID(); + sourceDomain, targetDomain, receiver, operator, amount, nonce, timestamp = gateway.teleportGUID(); assert(sourceDomain == guid.sourceDomain, "guid.sourceDomain was not preserved"); assert(targetDomain == guid.targetDomain, "guid.targetDomain was not preserved"); assert(receiver == guid.receiver, "guid.receiver was not preserved"); @@ -215,30 +243,24 @@ rule requestMint( assert(amount == guid.amount, "guid.amount was not preserved"); assert(nonce == guid.nonce, "guid.nonce was not preserved"); assert(timestamp == guid.timestamp, "guid.timestamp was not preserved"); - assert(join.maxFeePercentage() == maxFeePercentage, "maxFeePercentage was not preserved"); - assert(join.operatorFee() == operatorFee, "operatorFee was not preserved"); - assert(join.postFeeAmount() == postFeeAmount, "postFeeAmount was not preserved"); - assert(join.totalFee() == totalFee, "totalFee was not preserved"); } -// Verify revert rules on requestMint -rule requestMint_revert( - router.TeleportGUID guid, - uint256 maxFeePercentage, - uint256 operatorFee - ) { +// Verify revert rules on registerMint +rule registerMint_revert(router.TeleportGUID guid) { env e; - address targetGateway = gateways(guid.targetDomain); - require(targetGateway == join); - require(currentContract != targetGateway); - + address parentGateway = gateways(parentDomain()); address sourceGateway = gateways(guid.sourceDomain); + address targetGateway = gateways(guid.targetDomain) != 0x0000000000000000000000000000000000000000 + ? gateways(guid.targetDomain) + : parentGateway; + require(targetGateway == gateway); + require(currentContract != targetGateway); - requestMint@withrevert(e, guid, maxFeePercentage, operatorFee); + registerMint@withrevert(e, guid); bool revert1 = e.msg.value > 0; - bool revert2 = sourceGateway != e.msg.sender; + bool revert2 = parentGateway != e.msg.sender && sourceGateway != e.msg.sender; bool revert3 = targetGateway == 0x0000000000000000000000000000000000000000; assert(revert1 => lastReverted, "revert1 failed"); @@ -249,49 +271,54 @@ rule requestMint_revert( } // Verify that settle behaves correctly -rule settle(bytes32 targetDomain, uint256 batchedDaiToFlush) { +rule settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) { env e; - address targetGateway = gateways(targetDomain); - - require(targetGateway == join); + address targetGateway = gateways(targetDomain) != 0x0000000000000000000000000000000000000000 + ? gateways(targetDomain) + : gateways(parentDomain()); + require(targetGateway == gateway); require(currentContract != targetGateway); uint256 daiSenderBefore = dai.balanceOf(e.msg.sender); uint256 daiGatewayBefore = dai.balanceOf(targetGateway); - settle(e, targetDomain, batchedDaiToFlush); + settle(e, sourceDomain, targetDomain, amount); uint256 daiSenderAfter = dai.balanceOf(e.msg.sender); uint256 daiGatewayAfter = dai.balanceOf(targetGateway); - assert(join.sourceDomain() == domains(e.msg.sender), "sourceDomain was not preserved"); - assert(join.batchedDaiToFlush() == batchedDaiToFlush, "batchedDaiToFlush was not preserved"); - assert(e.msg.sender != targetGateway => daiSenderAfter == daiSenderBefore - batchedDaiToFlush, "Sender's DAI balance did not decrease as expected"); - assert(e.msg.sender != targetGateway => daiGatewayAfter == daiGatewayBefore + batchedDaiToFlush, "Gateway's DAI balance did not increase as expected"); + assert(gateway.sourceDomain() == sourceDomain, "sourceDomain was not preserved"); + assert(gateway.targetDomain() == targetDomain, "targetDomain was not preserved"); + assert(gateway.amount() == amount, "amount was not preserved"); + assert(e.msg.sender != targetGateway => daiSenderAfter == daiSenderBefore - amount, "Sender's DAI balance did not decrease as expected"); + assert(e.msg.sender != targetGateway => daiGatewayAfter == daiGatewayBefore + amount, "Gateway's DAI balance did not increase as expected"); } // Verify revert rules on settle -rule settle_revert(bytes32 targetDomain, uint256 batchedDaiToFlush) { +rule settle_revert(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) { env e; - address targetGateway = gateways(targetDomain); - require(targetGateway == join); + address parentGateway = gateways(parentDomain()); + address sourceGateway = gateways(sourceDomain); + address targetGateway = gateways(targetDomain) != 0x0000000000000000000000000000000000000000 + ? gateways(targetDomain) + : parentGateway; + require(targetGateway == gateway); require(currentContract != targetGateway); - bytes32 sourceDomain = domains(e.msg.sender); uint256 allowance = dai.allowance(e.msg.sender, currentContract); uint256 daiSender = dai.balanceOf(e.msg.sender); uint256 daiTarget = dai.balanceOf(targetGateway); - settle@withrevert(e, targetDomain, batchedDaiToFlush); + settle@withrevert(e, sourceDomain, targetDomain, amount); bool revert1 = e.msg.value > 0; - bool revert2 = sourceDomain == 0x0000000000000000000000000000000000000000000000000000000000000000; + bool revert2 = parentGateway != e.msg.sender && sourceGateway != e.msg.sender; bool revert3 = targetGateway == 0x0000000000000000000000000000000000000000; - bool revert4 = e.msg.sender != currentContract && allowance < batchedDaiToFlush; - bool revert5 = daiSender < batchedDaiToFlush; - bool revert6 = e.msg.sender != targetGateway && daiTarget + batchedDaiToFlush > max_uint256; + bool revert4 = e.msg.sender != currentContract && allowance < amount; + bool revert5 = daiSender < amount; + bool revert6 = e.msg.sender != targetGateway && daiTarget + amount > max_uint256; assert(revert1 => lastReverted, "revert1 failed"); assert(revert2 => lastReverted, "revert2 failed"); @@ -303,3 +330,187 @@ rule settle_revert(bytes32 targetDomain, uint256 batchedDaiToFlush) { assert(lastReverted => revert1 || revert2 || revert3 || revert4 || revert5 || revert6, "Revert rules are not covering all the cases"); } + +// Verify that initiateTeleport behaves correctly +rule initiateTeleport( + bytes32 _targetDomain, + bytes32 _receiver, + uint128 _amount, + bytes32 _operator + ) { + env e; + + uint256 option; + + require(e.block.timestamp <= max_uint48()); + + bytes32 domain = domain(); + + uint256 batchesTargetBefore = batches(_targetDomain); + uint80 nonceBefore = nonce(); + uint256 daiSenderBefore = dai.balanceOf(e.msg.sender); + uint256 daiRouterBefore = dai.balanceOf(currentContract); + + require(gateways(_targetDomain) == gateway); + + if (option == 0) { + initiateTeleport(e, _targetDomain, _receiver, _amount, _operator); + } else if (option == 1) { + initiateTeleport(e, _targetDomain, aux.bytes32ToAddress(_receiver), _amount, aux.bytes32ToAddress(_operator)); + } else { + initiateTeleport(e, _targetDomain, aux.bytes32ToAddress(_receiver), _amount); + } + + uint256 batchesTargetAfter = batches(_targetDomain); + uint80 nonceAfter = nonce(); + uint256 daiSenderAfter = dai.balanceOf(e.msg.sender); + uint256 daiRouterAfter = dai.balanceOf(currentContract); + + bytes32 sourceDomain; + bytes32 targetDomain; + bytes32 receiver; + bytes32 operator; + uint128 amount; + uint80 nonce; + uint48 timestamp; + sourceDomain, targetDomain, receiver, operator, amount, nonce, timestamp = gateway.teleportGUID(); + + assert(batchesTargetAfter == batchesTargetBefore + amount, "batchesTarget did not increase as expected"); + assert(nonceAfter == nonceBefore + 1, "nonce did not increase as expected"); + assert(e.msg.sender != currentContract => daiSenderAfter == daiSenderBefore - amount, "Sender's DAI balance did not decrease as expected"); + assert(e.msg.sender != currentContract => daiRouterAfter == daiRouterBefore + amount, "Router's DAI balance did not increase as expected"); + assert(sourceDomain == domain, "sourceDomain was not preserved"); + assert(targetDomain == _targetDomain, "targetDomain was not preserved"); + if (option == 0) { + assert(receiver == _receiver, "receiver was not preserved in option 1"); + assert(operator == _operator, "operator was not preserved in option 1"); + } else if (option == 1) { + assert(receiver == aux.addressToBytes32(aux.bytes32ToAddress(_receiver)), "receiver was not preserved in option 2"); + assert(operator == aux.addressToBytes32(aux.bytes32ToAddress(_operator)), "operator was not preserved in option 2"); + } else { + assert(receiver == aux.addressToBytes32(aux.bytes32ToAddress(_receiver)), "receiver was not preserved in option 3"); + assert(operator == 0x0000000000000000000000000000000000000000000000000000000000000000, "operator was not preserved in option 3"); + } + assert(amount == _amount, "amount was not preserved"); + assert(nonce == nonceBefore, "nonce was not preserved"); + assert(timestamp == e.block.timestamp, "timestamp was not preserved"); +} + +// Verify revert rules on initiateTeleport +rule initiateTeleport_revert( + bytes32 _targetDomain, + bytes32 _receiver, + uint128 _amount, + bytes32 _operator + ) { + env e; + + uint256 option; + + address targetGateway = gateways(_targetDomain) != 0x0000000000000000000000000000000000000000 + ? gateways(_targetDomain) + : gateways(parentDomain()); + require(targetGateway == gateway); + require(currentContract != targetGateway); + + uint256 batchesTarget = batches(_targetDomain); + uint80 nonce = nonce(); + uint256 allowance = dai.allowance(e.msg.sender, currentContract); + uint256 daiSender = dai.balanceOf(e.msg.sender); + uint256 daiRouter = dai.balanceOf(currentContract); + + if (option == 0) { + initiateTeleport@withrevert(e, _targetDomain, _receiver, _amount, _operator); + } else if (option == 1) { + initiateTeleport@withrevert(e, _targetDomain, aux.bytes32ToAddress(_receiver), _amount, aux.bytes32ToAddress(_operator)); + } else { + initiateTeleport@withrevert(e, _targetDomain, aux.bytes32ToAddress(_receiver), _amount); + } + + bool revert1 = e.msg.value > 0; + bool revert2 = nonce + 1 > max_uint80(); + bool revert3 = batchesTarget + to_uint256(_amount) > max_uint256; + bool revert4 = e.msg.sender != currentContract && allowance < _amount; + bool revert5 = daiSender < _amount; + bool revert6 = e.msg.sender != currentContract && daiRouter + _amount > max_uint256; + bool revert7 = targetGateway == 0x0000000000000000000000000000000000000000; + + assert(revert1 => lastReverted, "revert1 failed"); + assert(revert2 => lastReverted, "revert2 failed"); + assert(revert3 => lastReverted, "revert3 failed"); + assert(revert4 => lastReverted, "revert4 failed"); + assert(revert5 => lastReverted, "revert5 failed"); + assert(revert6 => lastReverted, "revert6 failed"); + assert(revert7 => lastReverted, "revert7 failed"); + + assert(lastReverted => revert1 || revert2 || revert3 || + revert4 || revert5 || revert6 || + revert7, "Revert rules are not covering all the cases"); +} + +// Verify that flush behaves correctly +rule flush(bytes32 _targetDomain) { + env e; + + address targetGateway = gateways(_targetDomain) != 0x0000000000000000000000000000000000000000 + ? gateways(_targetDomain) + : gateways(parentDomain()); + + bytes32 domain = domain(); + + uint256 batchesTargetBefore = batches(_targetDomain); + uint256 daiRouterBefore = dai.balanceOf(currentContract); + uint256 daiTargetBefore = dai.balanceOf(targetGateway); + + require(gateways(_targetDomain) == gateway); + + flush(e, _targetDomain); + + uint256 batchesTargetAfter = batches(_targetDomain); + uint256 daiRouterAfter = dai.balanceOf(currentContract); + uint256 daiTargetAfter = dai.balanceOf(targetGateway); + + bytes32 sourceDomain = gateway.sourceDomain(); + bytes32 targetDomain = gateway.targetDomain(); + uint256 amount = gateway.amount(); + + assert(batchesTargetAfter == 0, "batchesTarget did not decrease to 0"); + assert(targetGateway != currentContract => daiRouterAfter == daiRouterBefore - amount, "Router's DAI balance did not decrease as expected"); + assert(targetGateway != currentContract => daiTargetAfter == daiTargetBefore + amount, "Target's DAI balance did not increase as expected"); + assert(sourceDomain == domain, "sourceDomain was not preserved"); + assert(targetDomain == _targetDomain, "targetDomain was not preserved"); + assert(amount == batchesTargetBefore, "amount was not preserved"); +} + +// Verify revert rules on flush +rule flush_revert(bytes32 _targetDomain) { + env e; + + address targetGateway = gateways(_targetDomain) != 0x0000000000000000000000000000000000000000 + ? gateways(_targetDomain) + : gateways(parentDomain()); + require(targetGateway == gateway); + require(currentContract != targetGateway); + + uint256 batchesTarget = batches(_targetDomain); + uint256 fdust = fdust(); + uint256 daiRouter = dai.balanceOf(currentContract); + uint256 daiTarget = dai.balanceOf(targetGateway); + + flush@withrevert(e, _targetDomain); + + bool revert1 = e.msg.value > 0; + bool revert2 = batchesTarget < fdust; + bool revert3 = targetGateway == 0x0000000000000000000000000000000000000000; + bool revert4 = daiRouter < batchesTarget; + bool revert5 = targetGateway != currentContract && daiTarget + batchesTarget > max_uint256; + + assert(revert1 => lastReverted, "revert1 failed"); + assert(revert2 => lastReverted, "revert2 failed"); + assert(revert3 => lastReverted, "revert3 failed"); + assert(revert4 => lastReverted, "revert4 failed"); + assert(revert5 => lastReverted, "revert5 failed"); + + assert(lastReverted => revert1 || revert2 || revert3 || + revert4 || revert5, "Revert rules are not covering all the cases"); +} From ba4dafac3b48cb10a722259257a203b08e712083 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Thu, 6 Oct 2022 10:56:24 -0400 Subject: [PATCH 21/35] no need for dai approval --- src/TeleportRouter.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 64ea321..9c07c74 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -105,9 +105,6 @@ contract TeleportRouter { } gateways[_domain] = data; - if (data != address(0)) { - dai.approve(data, type(uint256).max); - } } else { revert("TeleportRouter/file-unrecognized-param"); } From e8bb7b70c47da2c9974b70242cab223353cd67a3 Mon Sep 17 00:00:00 2001 From: Gonzalo Balabasquer Date: Thu, 6 Oct 2022 12:59:10 -0300 Subject: [PATCH 22/35] Certora: update router spec with latest change --- certora/TeleportRouter.spec | 8 -------- 1 file changed, 8 deletions(-) diff --git a/certora/TeleportRouter.spec b/certora/TeleportRouter.spec index b0672de..0c2d829 100644 --- a/certora/TeleportRouter.spec +++ b/certora/TeleportRouter.spec @@ -136,10 +136,6 @@ rule file_domain_address(bytes32 what, bytes32 domain, address data) { assert( gateways(domain) == data, "file did not set gateways(domain) as expected" ); - assert( - !dataIsEmpty - => dai.allowance(currentContract, data) == max_uint256, "file did not set max allowance as expected" - ); assert( gatewayWasEmpty && !hasDomainBefore && !dataIsEmpty && numDomainsBefore < max_uint256 => numDomainsAfter == numDomainsBefore + 1, "file did not increase allDomains length as expected" @@ -149,10 +145,6 @@ rule file_domain_address(bytes32 what, bytes32 domain, address data) { !lastReverted && gatewayWasEmpty && !dataIsEmpty => domainAt == domain, "file did not modify allDomains as expected" ); - // assert( - // !gatewayWasEmpty && gatewayBefore != data - // => dai.allowance(currentContract, gatewayBefore) == 0, "file did not clear allowance as expected" - // ); assert( !gatewayWasEmpty && hasDomainBefore && dataIsEmpty => numDomainsAfter == numDomainsBefore - 1, "file did not decrease allDomains length as expected" From 0d95eff962d886a2bc767550f847579c0fea034d Mon Sep 17 00:00:00 2001 From: Gonzalo Balabasquer Date: Mon, 10 Oct 2022 11:11:26 -0300 Subject: [PATCH 23/35] Certora: Use optimization only for proved contracts and leave it flexible form ultiple solc versions --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 3d20875..a2595a4 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ all :; DAPP_BUILD_OPTIMIZE=1 DAPP_BUILD_OPTIMIZE_RUNS=200 dapp --use clean :; dapp clean test :; ./test.sh $(match) $(runs) cov :; DAPP_BUILD_OPTIMIZE=1 DAPP_BUILD_OPTIMIZE_RUNS=200 dapp --use solc:0.8.15 test -v --coverage --cov-match "src\/Teleport" -certora-fee :; certoraRun --solc ~/.solc-select/artifacts/solc-0.8.15 --solc_args "['--optimize','--optimize-runs','200']" --rule_sanity basic src/TeleportConstantFee.sol --verify TeleportConstantFee:certora/TeleportConstantFee.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output -certora-join :; certoraRun --solc ~/.solc-select/artifacts/solc-0.8.15 --solc_args "['--optimize','--optimize-runs','200']" --rule_sanity basic src/TeleportJoin.sol certora/FeesMock.sol certora/Auxiliar.sol src/test/mocks/VatMock.sol src/test/mocks/DaiMock.sol src/test/mocks/DaiJoinMock.sol --link TeleportJoin:vat=VatMock TeleportJoin:daiJoin=DaiJoinMock DaiJoinMock:vat=VatMock DaiJoinMock:dai=DaiMock --verify TeleportJoin:certora/TeleportJoin.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output -certora-router :; certoraRun --solc ~/.solc-select/artifacts/solc-0.8.15 --solc_args "['--optimize','--optimize-runs','200']" --rule_sanity basic src/TeleportRouter.sol certora/GatewayMock.sol src/test/mocks/DaiMock.sol certora/Auxiliar.sol --link TeleportRouter:dai=DaiMock --verify TeleportRouter:certora/TeleportRouter.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output -certora-oracle :; certoraRun --solc ~/.solc-select/artifacts/solc-0.8.15 --solc_args "['--optimize','--optimize-runs','200']" --rule_sanity basic src/TeleportOracleAuth.sol certora/TeleportJoinMock.sol certora/Auxiliar.sol --link TeleportOracleAuth:teleportJoin=TeleportJoinMock Auxiliar:oracle=TeleportOracleAuth --verify TeleportOracleAuth:certora/TeleportOracleAuth.spec --loop_iter 10 --optimistic_loop $(if $(rule),--rule $(rule),) --multi_assert_check --short_output +certora-fee :; PATH=~/.solc-select/artifacts:${PATH} certoraRun --solc_map TeleportConstantFee=solc-0.8.15 --optimize_map TeleportConstantFee=200 --rule_sanity basic src/TeleportConstantFee.sol --verify TeleportConstantFee:certora/TeleportConstantFee.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output +certora-join :; PATH=~/.solc-select/artifacts:${PATH} certoraRun --solc_map TeleportJoin=solc-0.8.15,FeesMock=solc-0.8.15,Auxiliar=solc-0.8.15,VatMock=solc-0.8.15,DaiMock=solc-0.8.15,DaiJoinMock=solc-0.8.15 --optimize_map TeleportJoin=200,FeesMock=0,Auxiliar=0,VatMock=0,DaiMock=0,DaiJoinMock=0 --rule_sanity basic src/TeleportJoin.sol certora/FeesMock.sol certora/Auxiliar.sol src/test/mocks/VatMock.sol src/test/mocks/DaiMock.sol src/test/mocks/DaiJoinMock.sol --link TeleportJoin:vat=VatMock TeleportJoin:daiJoin=DaiJoinMock DaiJoinMock:vat=VatMock DaiJoinMock:dai=DaiMock --verify TeleportJoin:certora/TeleportJoin.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output +certora-router :; PATH=~/.solc-select/artifacts:${PATH} certoraRun --solc_map TeleportRouter=solc-0.8.15,GatewayMock=solc-0.8.15,DaiMock=solc-0.8.15,Auxiliar=solc-0.8.15 --optimize_map TeleportRouter=200,GatewayMock=0,DaiMock=0,Auxiliar=0 --rule_sanity basic src/TeleportRouter.sol certora/GatewayMock.sol src/test/mocks/DaiMock.sol certora/Auxiliar.sol --link TeleportRouter:dai=DaiMock --verify TeleportRouter:certora/TeleportRouter.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output +certora-oracle :; PATH=~/.solc-select/artifacts:${PATH} certoraRun --solc_map TeleportOracleAuth=solc-0.8.15,TeleportJoinMock=solc-0.8.15,Auxiliar=solc-0.8.15 --optimize_map TeleportOracleAuth=200,TeleportJoinMock=0,Auxiliar=0 --rule_sanity basic src/TeleportOracleAuth.sol certora/TeleportJoinMock.sol certora/Auxiliar.sol --link TeleportOracleAuth:teleportJoin=TeleportJoinMock Auxiliar:oracle=TeleportOracleAuth --verify TeleportOracleAuth:certora/TeleportOracleAuth.spec --loop_iter 10 --optimistic_loop $(if $(rule),--rule $(rule),) --multi_assert_check --short_output From 24fe4467a57db8ed8865885b5e8fad80a3a623d6 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Mon, 24 Oct 2022 12:52:35 -0400 Subject: [PATCH 24/35] fix revert; remove interface names --- src/TeleportRouter.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 9c07c74..60d0f55 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -25,8 +25,8 @@ interface TokenLike { } interface GatewayLike { - function registerMint(TeleportGUID calldata teleportGUID) external; - function settle(bytes32 sourceDomain, bytes32 targetDomain, uint256 amount) external; + function registerMint(TeleportGUID calldata) external; + function settle(bytes32, bytes32, uint256) external; } contract TeleportRouter { @@ -115,7 +115,7 @@ contract TeleportRouter { if (what == "fdust") { fdust = data; } else { - revert("TeleportJoin/file-unrecognized-param"); + revert("TeleportRouter/file-unrecognized-param"); } emit File(what, data); } From c5b66df6f95d77d625215c1e6ef147b6a1082d91 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Mon, 24 Oct 2022 13:01:43 -0400 Subject: [PATCH 25/35] spacing --- src/TeleportRouter.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol index 60d0f55..628d763 100644 --- a/src/TeleportRouter.sol +++ b/src/TeleportRouter.sol @@ -91,14 +91,14 @@ contract TeleportRouter { function file(bytes32 what, bytes32 _domain, address data) external auth { if (what == "gateway") { address prevGateway = gateways[_domain]; - if(prevGateway == address(0)) { + if (prevGateway == address(0)) { // new domain => add it to allDomains - if(data != address(0)) { + if (data != address(0)) { allDomains.add(_domain); } } else { // existing domain - if(data == address(0)) { + if (data == address(0)) { // => remove domain from allDomains allDomains.remove(_domain); } From 3cbd5570d76716793c7c61ce3054aedce5176e52 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Tue, 25 Oct 2022 06:03:35 -0400 Subject: [PATCH 26/35] Update src/test/TeleportJoin.t.sol Co-authored-by: Gonzalo Balabasquer --- src/test/TeleportJoin.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index 6a03068..a29ce7b 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -517,8 +517,7 @@ contract TeleportJoinTest is DSTest { assertEq(join.debt("l2network"), 0); vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(this), 100_000 ether); - dai.transfer(address(join), 100_000 ether); + daiJoin.exit(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); From 9e0d7fddda0b36ebd0f2ba083333cdd778ab5626 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Tue, 25 Oct 2022 06:03:44 -0400 Subject: [PATCH 27/35] Update src/test/TeleportJoin.t.sol Co-authored-by: Gonzalo Balabasquer --- src/test/TeleportJoin.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index a29ce7b..78c31d8 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -527,8 +527,7 @@ contract TeleportJoinTest is DSTest { function testWithdrawNegativeDebt() public { vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(this), 100_000 ether); - dai.transfer(address(join), 100_000 ether); + daiJoin.exit(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); From 821e11ca6edeb419122533f2915c1845221fa64a Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Tue, 25 Oct 2022 06:03:51 -0400 Subject: [PATCH 28/35] Update src/test/TeleportJoin.t.sol Co-authored-by: Gonzalo Balabasquer --- src/test/TeleportJoin.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index 78c31d8..33ae526 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -554,8 +554,7 @@ contract TeleportJoinTest is DSTest { function testWithdrawPartialNegativeDebt() public { vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(this), 100_000 ether); - dai.transfer(address(join), 100_000 ether); + daiJoin.exit(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); From e46ec8a9b95166978867d4a912e75d252bcb5f9d Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Tue, 25 Oct 2022 06:04:04 -0400 Subject: [PATCH 29/35] Update src/test/TeleportJoin.t.sol Co-authored-by: Gonzalo Balabasquer --- src/test/TeleportJoin.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index 33ae526..53f96fb 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -583,8 +583,7 @@ contract TeleportJoinTest is DSTest { function testWithdrawVatCaged() public { vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(this), 100_000 ether); - dai.transfer(address(join), 100_000 ether); + daiJoin.exit(address(join), 100_000 ether); join.settle("l2network", domain, 100_000 ether); From 52c5bd212ed41aeb5afb95266a10af53628f7c5c Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Tue, 25 Oct 2022 06:04:14 -0400 Subject: [PATCH 30/35] Update src/test/TeleportJoin.t.sol Co-authored-by: Gonzalo Balabasquer --- src/test/TeleportJoin.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index 53f96fb..6c1d486 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -635,8 +635,7 @@ contract TeleportJoinTest is DSTest { vat.cage(); vat.suck(address(0), address(this), 250_000 * RAD); - daiJoin.exit(address(this), 250_000 ether); - dai.transfer(address(join), 250_000 ether); + daiJoin.exit(address(join), 250_000 ether); join.settle("l2network", domain, 250_000 ether); From f0fd5370ae134d403a89f186917f930788e4ea69 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Tue, 25 Oct 2022 06:04:38 -0400 Subject: [PATCH 31/35] Update src/test/TeleportJoin.t.sol Co-authored-by: Gonzalo Balabasquer --- src/test/TeleportJoin.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index 6c1d486..5c4401d 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -753,8 +753,8 @@ contract TeleportJoinTest is DSTest { join.file("fees", "l2network_3", address(new TeleportConstantFee(0, TTL))); vat.suck(address(0), address(this), 100_000 * RAD); - daiJoin.exit(address(this), 100_000 ether); - dai.transfer(address(join), 100_000 ether); + daiJoin.exit(address(join), 100_000 ether); + join.settle("l2network", domain, 100_000 ether); TeleportGUID memory guid = TeleportGUID({ From f4b09e29893fcac678f3c2fa2f4d7dd264df2b53 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Tue, 25 Oct 2022 06:04:51 -0400 Subject: [PATCH 32/35] Update src/test/TeleportJoin.t.sol Co-authored-by: Gonzalo Balabasquer --- src/test/TeleportJoin.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index 5c4401d..6e472fd 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -801,8 +801,8 @@ contract TeleportJoinTest is DSTest { assertEq(join.cure(), 200_000 * RAD); vat.suck(address(0), address(this), 10_000 * RAD); - daiJoin.exit(address(this), 10_000 ether); - dai.transfer(address(join), 10_000 ether); + daiJoin.exit(address(join), 10_000 ether); + join.settle("l2network_3", domain, 10_000 ether); assertEq(join.debt("l2network"), -50_000 ether); From 1c1bef383661167c311ee1ed63bc39c4f080aa3d Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Tue, 25 Oct 2022 06:07:52 -0400 Subject: [PATCH 33/35] remove interface params --- src/test/integration/TeleportJoin.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/integration/TeleportJoin.t.sol b/src/test/integration/TeleportJoin.t.sol index 6d5fec1..10d8fb8 100644 --- a/src/test/integration/TeleportJoin.t.sol +++ b/src/test/integration/TeleportJoin.t.sol @@ -39,7 +39,7 @@ interface VatLike { function wards(address) external view returns (uint256); function rely(address) external; function init(bytes32) external; - function file(bytes32 ilk, bytes32 what, uint data) external; + function file(bytes32, bytes32, uint256) external; function dai(address) external view returns (uint256); function debt() external view returns (uint256); } @@ -58,7 +58,7 @@ interface CureLike { } interface TokenLike { - function transfer(address _to, uint256 _value) external returns (bool success); + function transfer(address, uint256) external returns (bool); function approve(address, uint256) external returns (bool); } From 24cc1729d9ee042853a14f8feaefb516f40f0fec Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Tue, 25 Oct 2022 06:10:32 -0400 Subject: [PATCH 34/35] remove unused file test --- src/test/TeleportRouter.t.sol | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/test/TeleportRouter.t.sol b/src/test/TeleportRouter.t.sol index e5690f6..1a6c444 100644 --- a/src/test/TeleportRouter.t.sol +++ b/src/test/TeleportRouter.t.sol @@ -51,10 +51,6 @@ contract TeleportRouterTest is DSTest { (ok,) = address(router).call(abi.encodeWithSignature("file(bytes32,bytes32,address)", what, _domain, data)); } - function _tryFile(bytes32 what, address data) internal returns (bool ok) { - (ok,) = address(router).call(abi.encodeWithSignature("file(bytes32,address)", what, data)); - } - function _tryFile(bytes32 what, uint256 data) internal returns (bool ok) { (ok,) = address(router).call(abi.encodeWithSignature("file(bytes32,uint256)", what, data)); } @@ -252,7 +248,6 @@ contract TeleportRouterTest is DSTest { function testFileInvalidWhat() public { assertTrue(!_tryFile("meh", "aaa", address(888))); - assertTrue(!_tryFile("meh", address(888))); } function testFileFailsWhenNotAuthed() public { From 5f880a81dbaba1150a5501417c43cf505ffe7fda Mon Sep 17 00:00:00 2001 From: Gonzalo Balabasquer Date: Tue, 17 Jan 2023 14:40:22 -0300 Subject: [PATCH 35/35] Migrate to forge (#102) * Migrate to forge * dapp uninstall ds-test * dapp install foundry-rs/forge-std * Use forge-std and replace hevm by vm * Fix config.yaml * Replace try functions with forge way to evaluate reverts + removing cache directory from repository * Add env variable * Fix imports * Remove shell.nix * Use older version of prover until some issues are fixed * Update src/test/TeleportOracleAuth.t.sol Co-authored-by: olivdb * Add certain form of coverage back Co-authored-by: olivdb --- .github/workflows/config.yaml | 32 ++--- .gitignore | 4 + .gitmodules | 6 +- Makefile | 18 +-- coverage.sh | 16 --- lib/ds-test | 1 - lib/forge-std | 1 + shell.nix | 19 --- src/test/TeleportConstantFee.t.sol | 8 +- src/test/TeleportJoin.t.sol | 165 ++++++++++-------------- src/test/TeleportLinearFee.t.sol | 8 +- src/test/TeleportOracleAuth.t.sol | 83 ++++-------- src/test/TeleportRouter.t.sol | 137 +++++++++----------- src/test/integration/TeleportJoin.t.sol | 13 +- test.sh | 16 +-- 15 files changed, 199 insertions(+), 328 deletions(-) delete mode 100755 coverage.sh delete mode 160000 lib/ds-test create mode 160000 lib/forge-std delete mode 100644 shell.nix diff --git a/.github/workflows/config.yaml b/.github/workflows/config.yaml index 5e2d218..bee7b65 100644 --- a/.github/workflows/config.yaml +++ b/.github/workflows/config.yaml @@ -4,28 +4,20 @@ jobs: tests: runs-on: ubuntu-latest steps: - - name: Checkout repository and submodules - uses: actions/checkout@v2 - with: - submodules: recursive - - - name: Install nix 2.3.6 - uses: cachix/install-nix-action@v13 - with: - install_url: https://releases.nixos.org/nix/nix-2.3.6/install - nix_path: nixpkgs=channel:nixos-unstable + - name: Checkout repository and submodules + uses: actions/checkout@v3 + with: + submodules: recursive - - name: Use maker and dapp cachix - uses: cachix/cachix-action@v10 - with: - name: maker - extraPullNames: dapp + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly - - name: Run tests - run: nix-shell --pure --argstr url ${{ secrets.ETH_RPC_URL }} --run 'dapp test -v --rpc' - - - name: Run coverage - run: nix-shell --pure --argstr url ${{ secrets.ETH_RPC_URL }} --run 'bash ./coverage.sh' + - name: Run tests + run: make test + env: + ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} solhint: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 98c5c35..a8b2cfc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ /out +/cache + +coverage/ +lcov.info # certora .*certora* diff --git a/.gitmodules b/.gitmodules index e124719..888d42d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "lib/ds-test"] - path = lib/ds-test - url = https://github.com/dapphub/ds-test +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/Makefile b/Makefile index 4b9d213..119bdc2 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ -all :; DAPP_BUILD_OPTIMIZE=1 DAPP_BUILD_OPTIMIZE_RUNS=200 dapp --use solc:0.8.15 build -clean :; dapp clean -test :; ./test.sh $(match) $(runs) -cov :; DAPP_BUILD_OPTIMIZE=1 DAPP_BUILD_OPTIMIZE_RUNS=200 dapp --use solc:0.8.15 test -v --coverage --cov-match "src\/Teleport" -certora-con-fee :; PATH=~/.solc-select/artifacts/solc-0.8.15:${PATH} certoraRun --solc_map TeleportConstantFee=solc-0.8.15 --optimize_map TeleportConstantFee=200 --rule_sanity basic src/TeleportConstantFee.sol --verify TeleportConstantFee:certora/TeleportConstantFee.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output -certora-lin-fee :; PATH=~/.solc-select/artifacts/solc-0.8.15:${PATH} certoraRun --solc_map TeleportLinearFee=solc-0.8.15 --optimize_map TeleportLinearFee=200 --rule_sanity basic src/TeleportLinearFee.sol --verify TeleportLinearFee:certora/TeleportLinearFee.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output -certora-join :; PATH=~/.solc-select/artifacts/solc-0.8.15:${PATH} certoraRun --solc_map TeleportJoin=solc-0.8.15,FeesMock=solc-0.8.15,Auxiliar=solc-0.8.15,VatMock=solc-0.8.15,DaiMock=solc-0.8.15,DaiJoinMock=solc-0.8.15 --optimize_map TeleportJoin=200,FeesMock=0,Auxiliar=0,VatMock=0,DaiMock=0,DaiJoinMock=0 --rule_sanity basic src/TeleportJoin.sol certora/FeesMock.sol certora/Auxiliar.sol src/test/mocks/VatMock.sol src/test/mocks/DaiMock.sol src/test/mocks/DaiJoinMock.sol --link TeleportJoin:vat=VatMock TeleportJoin:daiJoin=DaiJoinMock DaiJoinMock:vat=VatMock DaiJoinMock:dai=DaiMock --verify TeleportJoin:certora/TeleportJoin.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output -certora-router :; PATH=~/.solc-select/artifacts/solc-0.8.15:${PATH} certoraRun --solc_map TeleportRouter=solc-0.8.15,GatewayMock=solc-0.8.15,DaiMock=solc-0.8.15,Auxiliar=solc-0.8.15 --optimize_map TeleportRouter=200,GatewayMock=0,DaiMock=0,Auxiliar=0 --rule_sanity basic src/TeleportRouter.sol certora/GatewayMock.sol src/test/mocks/DaiMock.sol certora/Auxiliar.sol --link TeleportRouter:dai=DaiMock --verify TeleportRouter:certora/TeleportRouter.spec $(if $(rule),--rule $(rule),) --multi_assert_check --short_output -certora-oracle :; PATH=~/.solc-select/artifacts/solc-0.8.15:${PATH} certoraRun --solc_map TeleportOracleAuth=solc-0.8.15,TeleportJoinMock=solc-0.8.15,Auxiliar=solc-0.8.15 --optimize_map TeleportOracleAuth=200,TeleportJoinMock=0,Auxiliar=0 --rule_sanity basic src/TeleportOracleAuth.sol certora/TeleportJoinMock.sol certora/Auxiliar.sol --link TeleportOracleAuth:teleportJoin=TeleportJoinMock Auxiliar:oracle=TeleportOracleAuth --verify TeleportOracleAuth:certora/TeleportOracleAuth.spec --loop_iter 10 --optimistic_loop $(if $(rule),--rule $(rule),) --multi_assert_check --short_output +all :; FOUNDRY_OPTIMIZER=true FOUNDRY_OPTIMIZER_RUNS=200 forge build --use solc:0.8.15 +clean :; forge clean +test :; ./test.sh $(match) +cov :; rm -rf coverage && mkdir coverage && forge coverage --report lcov && lcov -r lcov.info 'src/test/*' 'src/utils/*' -o lcov.info && genhtml lcov.info --output-directory coverage +certora-con-fee :; PATH=~/.solc-select/artifacts/solc-0.8.15:${PATH} certoraRun --solc_map TeleportConstantFee=solc-0.8.15 --optimize_map TeleportConstantFee=200 --rule_sanity basic src/TeleportConstantFee.sol --verify TeleportConstantFee:certora/TeleportConstantFee.spec $(if $(rule),--rule $(rule),) --cloud release/19Oct2022 --multi_assert_check --short_output +certora-lin-fee :; PATH=~/.solc-select/artifacts/solc-0.8.15:${PATH} certoraRun --solc_map TeleportLinearFee=solc-0.8.15 --optimize_map TeleportLinearFee=200 --rule_sanity basic src/TeleportLinearFee.sol --verify TeleportLinearFee:certora/TeleportLinearFee.spec $(if $(rule),--rule $(rule),) --cloud release/19Oct2022 --multi_assert_check --short_output +certora-join :; PATH=~/.solc-select/artifacts/solc-0.8.15:${PATH} certoraRun --solc_map TeleportJoin=solc-0.8.15,FeesMock=solc-0.8.15,Auxiliar=solc-0.8.15,VatMock=solc-0.8.15,DaiMock=solc-0.8.15,DaiJoinMock=solc-0.8.15 --optimize_map TeleportJoin=200,FeesMock=0,Auxiliar=0,VatMock=0,DaiMock=0,DaiJoinMock=0 --rule_sanity basic src/TeleportJoin.sol certora/FeesMock.sol certora/Auxiliar.sol src/test/mocks/VatMock.sol src/test/mocks/DaiMock.sol src/test/mocks/DaiJoinMock.sol --link TeleportJoin:vat=VatMock TeleportJoin:daiJoin=DaiJoinMock DaiJoinMock:vat=VatMock DaiJoinMock:dai=DaiMock --verify TeleportJoin:certora/TeleportJoin.spec $(if $(rule),--rule $(rule),) --cloud release/19Oct2022 --multi_assert_check --short_output +certora-router :; PATH=~/.solc-select/artifacts/solc-0.8.15:${PATH} certoraRun --solc_map TeleportRouter=solc-0.8.15,GatewayMock=solc-0.8.15,DaiMock=solc-0.8.15,Auxiliar=solc-0.8.15 --optimize_map TeleportRouter=200,GatewayMock=0,DaiMock=0,Auxiliar=0 --rule_sanity basic src/TeleportRouter.sol certora/GatewayMock.sol src/test/mocks/DaiMock.sol certora/Auxiliar.sol --link TeleportRouter:dai=DaiMock --verify TeleportRouter:certora/TeleportRouter.spec $(if $(rule),--rule $(rule),) --cloud release/19Oct2022 --multi_assert_check --short_output +certora-oracle :; PATH=~/.solc-select/artifacts/solc-0.8.15:${PATH} certoraRun --solc_map TeleportOracleAuth=solc-0.8.15,TeleportJoinMock=solc-0.8.15,Auxiliar=solc-0.8.15 --optimize_map TeleportOracleAuth=200,TeleportJoinMock=0,Auxiliar=0 --rule_sanity basic src/TeleportOracleAuth.sol certora/TeleportJoinMock.sol certora/Auxiliar.sol --link TeleportOracleAuth:teleportJoin=TeleportJoinMock Auxiliar:oracle=TeleportOracleAuth --verify TeleportOracleAuth:certora/TeleportOracleAuth.spec --loop_iter 10 --optimistic_loop $(if $(rule),--rule $(rule),) --cloud release/19Oct2022 --multi_assert_check --short_output diff --git a/coverage.sh b/coverage.sh deleted file mode 100755 index 24964c3..0000000 --- a/coverage.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -e -MAX_UNCOVERED=0 # Maximum number of uncovered lines allowed - -echo "Running coverage..." -uncovered=$(DAPP_BUILD_OPTIMIZE=1 DAPP_BUILD_OPTIMIZE_RUNS=200 dapp --use solc:0.8.15 test -v --rpc --coverage --cov-match "src\/Teleport" | grep "\[31m" | wc -l) -echo "Uncovered lines: $uncovered" - -if [[ $uncovered -gt $MAX_UNCOVERED ]]; then - echo "Insufficient coverage (max $MAX_UNCOVERED uncovered lines allowed)" - exit 1 -fi - -echo "Satisfying coverage!" - diff --git a/lib/ds-test b/lib/ds-test deleted file mode 160000 index 0a5da56..0000000 --- a/lib/ds-test +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0a5da56b0d65960e6a994d2ec8245e6edd38c248 diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..405727d --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 405727dc7580aaaa6e5b626638dd37ff171beae3 diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 696d778..0000000 --- a/shell.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ url, dappPkgs ? ( - import (fetchTarball "https://github.com/makerdao/makerpkgs/tarball/master") {} - ).dappPkgsVersions.master-20220803 -}: with dappPkgs; - -mkShell { - DAPP_SOLC = solc-static-versions.solc_0_8_15 + "/bin/solc-0.8.15"; - buildInputs = [ - dapp - ]; - - shellHook = '' - export NIX_SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt - export DAPP_BUILD_OPTIMIZE=1 - export DAPP_BUILD_OPTIMIZE_RUNS=200 - export ETH_RPC_URL="''${ETH_RPC_URL:-${url}}" - unset SSL_CERT_FILE - ''; -} diff --git a/src/test/TeleportConstantFee.t.sol b/src/test/TeleportConstantFee.t.sol index 78e06d9..fa6f533 100644 --- a/src/test/TeleportConstantFee.t.sol +++ b/src/test/TeleportConstantFee.t.sol @@ -16,7 +16,7 @@ pragma solidity 0.8.15; -import "ds-test/test.sol"; +import "forge-std/Test.sol"; import "src/TeleportConstantFee.sol"; import "src/TeleportGUID.sol"; @@ -25,9 +25,7 @@ interface Hevm { function warp(uint) external; } -contract TeleportConstantFeeTest is DSTest { - - Hevm internal hevm = Hevm(HEVM_ADDRESS); +contract TeleportConstantFeeTest is Test { uint256 internal fee = 1 ether / 100; uint256 internal ttl = 8 days; @@ -94,7 +92,7 @@ contract TeleportConstantFeeTest is DSTest { nonce: 5, timestamp: uint48(block.timestamp) }); - hevm.warp(block.timestamp + ttl); + vm.warp(block.timestamp + ttl); assertEq(teleportConstantFee.getFee(guid, 0, 0, 0, 100 ether), 0); } diff --git a/src/test/TeleportJoin.t.sol b/src/test/TeleportJoin.t.sol index 7740767..20fa23a 100644 --- a/src/test/TeleportJoin.t.sol +++ b/src/test/TeleportJoin.t.sol @@ -16,7 +16,7 @@ pragma solidity 0.8.15; -import "ds-test/test.sol"; +import "forge-std/Test.sol"; import "src/TeleportJoin.sol"; import "src/TeleportConstantFee.sol"; @@ -32,9 +32,7 @@ interface Hevm { function store(address, bytes32, bytes32) external; } -contract TeleportJoinTest is DSTest { - - Hevm internal hevm = Hevm(HEVM_ADDRESS); +contract TeleportJoinTest is Test { bytes32 constant internal ilk = "L2DAI"; bytes32 constant internal domain = "ethereum"; TeleportJoin internal join; @@ -74,52 +72,6 @@ contract TeleportJoinTest is DSTest { (, pending_) = join.teleports(getGUIDHash(guid)); } - function _tryRely(address usr) internal returns (bool ok) { - (ok,) = address(join).call(abi.encodeWithSignature("rely(address)", usr)); - } - - function _tryDeny(address usr) internal returns (bool ok) { - (ok,) = address(join).call(abi.encodeWithSignature("deny(address)", usr)); - } - - function _tryFile(bytes32 what, address data) internal returns (bool ok) { - (ok,) = address(join).call(abi.encodeWithSignature("file(bytes32,address)", what, data)); - } - - function _tryFile(bytes32 what, bytes32 domain_, address data) internal returns (bool ok) { - (ok,) = address(join).call(abi.encodeWithSignature("file(bytes32,bytes32,address)", what, domain_, data)); - } - - function _tryFile(bytes32 what, bytes32 domain_, uint256 data) internal returns (bool ok) { - (ok,) = address(join).call(abi.encodeWithSignature("file(bytes32,bytes32,uint256)", what, domain_, data)); - } - - function _tryRequestMint( - TeleportGUID memory teleportGUID, - uint256 maxFeePercentage, - uint256 operatorFee - ) internal returns (bool ok) { - (ok,) = address(join).call(abi.encodeWithSignature( - "requestMint((bytes32,bytes32,bytes32,bytes32,uint128,uint80,uint48),uint256,uint256)", - teleportGUID, - maxFeePercentage, - operatorFee - )); - } - - function _tryMintPending( - TeleportGUID memory teleportGUID, - uint256 maxFeePercentage, - uint256 operatorFee - ) internal returns (bool ok) { - (ok,) = address(join).call(abi.encodeWithSignature( - "mintPending((bytes32,bytes32,bytes32,bytes32,uint128,uint80,uint48),uint256,uint256)", - teleportGUID, - maxFeePercentage, - operatorFee - )); - } - function testConstructor() public { assertEq(address(join.vat()), address(vat)); assertEq(address(join.daiJoin()), address(daiJoin)); @@ -130,44 +82,53 @@ contract TeleportJoinTest is DSTest { function testRelyDeny() public { assertEq(join.wards(address(456)), 0); - assertTrue(_tryRely(address(456))); + join.rely(address(456)); assertEq(join.wards(address(456)), 1); - assertTrue(_tryDeny(address(456))); + join.deny(address(456)); assertEq(join.wards(address(456)), 0); join.deny(address(this)); - assertTrue(!_tryRely(address(456))); - assertTrue(!_tryDeny(address(456))); + vm.expectRevert("TeleportJoin/not-authorized"); + join.rely(address(456)); + vm.expectRevert("TeleportJoin/not-authorized"); + join.deny(address(456)); } function testFile() public { assertEq(join.vow(), vow); - assertTrue(_tryFile("vow", address(888))); + join.file("vow", address(888)); assertEq(join.vow(), address(888)); assertEq(join.fees("aaa"), address(0)); - assertTrue(_tryFile("fees", "aaa", address(888))); + join.file("fees", "aaa", address(888)); assertEq(join.fees("aaa"), address(888)); assertEq(join.line("aaa"), 0); uint256 maxInt256 = uint256(type(int256).max); - assertTrue(_tryFile("line", "aaa", maxInt256)); + join.file("line", "aaa", maxInt256); assertEq(join.line("aaa"), maxInt256); - assertTrue(!_tryFile("line", "aaa", maxInt256 + 1)); + vm.expectRevert("TeleportJoin/not-allowed-bigger-int256"); + join.file("line", "aaa", maxInt256 + 1); join.deny(address(this)); - assertTrue(!_tryFile("vow", address(888))); - assertTrue(!_tryFile("fees", "aaa", address(888))); - assertTrue(!_tryFile("line", "aaa", 10)); + vm.expectRevert("TeleportJoin/not-authorized"); + join.file("vow", address(888)); + vm.expectRevert("TeleportJoin/not-authorized"); + join.file("fees", "aaa", address(888)); + vm.expectRevert("TeleportJoin/not-authorized"); + join.file("line", "aaa", 10); } function testInvalidWhat() public { - assertTrue(!_tryFile("meh", address(888))); - assertTrue(!_tryFile("meh", domain, address(888))); - assertTrue(!_tryFile("meh", domain, 888)); + vm.expectRevert("TeleportJoin/file-unrecognized-param"); + join.file("meh", address(888)); + vm.expectRevert("TeleportJoin/file-unrecognized-param"); + join.file("meh", domain, address(888)); + vm.expectRevert("TeleportJoin/file-unrecognized-param"); + join.file("meh", domain, 888); } function testRegister() public { @@ -284,8 +245,9 @@ contract TeleportJoinTest is DSTest { nonce: 5, timestamp: uint48(block.timestamp) }); - assertTrue(_tryRequestMint(guid, 0, 0)); - assertTrue(!_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); + vm.expectRevert("TeleportJoin/already-blessed"); + join.requestMint(guid, 0, 0); } function testRegisterWrongDomain() public { @@ -298,7 +260,8 @@ contract TeleportJoinTest is DSTest { nonce: 5, timestamp: uint48(block.timestamp) }); - assertTrue(!_tryRequestMint(guid, 0, 0)); + vm.expectRevert("TeleportJoin/incorrect-domain"); + join.requestMint(guid, 0, 0); } function testRegisterAndWithdrawPayingFee() public { @@ -340,7 +303,8 @@ contract TeleportJoinTest is DSTest { }); join.file("fees", "l2network", address(new TeleportConstantFee(100 ether, TTL))); - assertTrue(!_tryRequestMint(guid, 3 * WAD / 10000, 0)); // 0.03% * 250K < 100 (not enough) + vm.expectRevert("TeleportJoin/max-fee-exceed"); + join.requestMint(guid, 3 * WAD / 10000, 0); // 0.03% * 250K < 100 (not enough) } function testRegisterAndWithdrawFeeTTLExpires() public { @@ -358,8 +322,8 @@ contract TeleportJoinTest is DSTest { assertEq(fees.fee(), 100 ether); join.file("fees", "l2network", address(fees)); - hevm.warp(block.timestamp + TTL + 1 days); // Over ttl - you don't pay fees - assertTrue(_tryRequestMint(guid, 0, 0)); + vm.warp(block.timestamp + TTL + 1 days); // Over ttl - you don't pay fees + join.requestMint(guid, 0, 0); assertEq(vat.dai(vow), 0); assertEq(dai.balanceOf(address(123)), 250_000 ether); @@ -384,7 +348,7 @@ contract TeleportJoinTest is DSTest { join.file("line", "l2network", 200_000 ether); join.file("fees", "l2network", address(new TeleportConstantFee(100 ether, TTL))); - assertTrue(_tryRequestMint(guid, 4 * WAD / 10000, 0)); // 0.04% * 200K = 80 (just enough as fee is also proportional) + join.requestMint(guid, 4 * WAD / 10000, 0); // 0.04% * 200K = 80 (just enough as fee is also proportional) assertEq(vat.dai(vow), 80 * RAD); assertEq(dai.balanceOf(address(123)), 199_920 ether); @@ -396,7 +360,7 @@ contract TeleportJoinTest is DSTest { join.file("line", "l2network", 250_000 ether); - assertTrue(_tryMintPending(guid, 4 * WAD / 10000, 0)); // 0.04% * 50 = 20 (just enough as fee is also proportional) + join.mintPending(guid, 4 * WAD / 10000, 0); // 0.04% * 50 = 20 (just enough as fee is also proportional) assertEq(vat.dai(vow), 100 * RAD); assertEq(dai.balanceOf(address(123)), 249_900 ether); @@ -421,7 +385,8 @@ contract TeleportJoinTest is DSTest { join.file("line", "l2network", 200_000 ether); join.file("fees", "l2network", address(new TeleportConstantFee(100 ether, TTL))); - assertTrue(!_tryRequestMint(guid, 3 * WAD / 10000, 0)); // 0.03% * 200K < 80 (not enough) + vm.expectRevert("TeleportJoin/max-fee-exceed"); + join.requestMint(guid, 3 * WAD / 10000, 0); // 0.03% * 200K < 80 (not enough) } function testRegisterAndWithdrawPartialPayingFee2() public { @@ -439,11 +404,12 @@ contract TeleportJoinTest is DSTest { join.file("line", "l2network", 200_000 ether); join.file("fees", "l2network", address(new TeleportConstantFee(100 ether, TTL))); - assertTrue(_tryRequestMint(guid, 4 * WAD / 10000, 0)); + join.requestMint(guid, 4 * WAD / 10000, 0); join.file("line", "l2network", 250_000 ether); - assertTrue(!_tryMintPending(guid, 3 * WAD / 10000, 0)); // 0.03% * 50 < 20 (not enough) + vm.expectRevert("TeleportJoin/max-fee-exceed"); + join.mintPending(guid, 3 * WAD / 10000, 0); // 0.03% * 50 < 20 (not enough) } function testMintPendingByOperator() public { @@ -458,14 +424,14 @@ contract TeleportJoinTest is DSTest { }); join.file("line", "l2network", 200_000 ether); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); assertEq(dai.balanceOf(address(this)), 200_000 ether); assertTrue(_blessed(guid)); assertEq(_pending(guid), 50_000 ether); join.file("line", "l2network", 225_000 ether); - assertTrue(_tryMintPending(guid, 0, 0)); + join.mintPending(guid, 0, 0); assertEq(dai.balanceOf(address(this)), 225_000 ether); assertEq(_pending(guid), 25_000 ether); @@ -483,14 +449,14 @@ contract TeleportJoinTest is DSTest { }); join.file("line", "l2network", 200_000 ether); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); assertEq(dai.balanceOf(address(123)), 200_000 ether); assertTrue(_blessed(guid)); assertEq(_pending(guid), 50_000 ether); join.file("line", "l2network", 225_000 ether); - assertTrue(_tryMintPending(guid, 0, 0)); + join.mintPending(guid, 0, 0); assertEq(dai.balanceOf(address(123)), 225_000 ether); assertEq(_pending(guid), 25_000 ether); @@ -508,14 +474,14 @@ contract TeleportJoinTest is DSTest { }); join.file("line", "l2network", 200_000 ether); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); assertEq(dai.balanceOf(address(this)), 200_000 ether); assertTrue(_blessed(guid)); assertEq(_pending(guid), 50_000 ether); join.file("line", "l2network", 225_000 ether); - assertTrue(_tryMintPending(guid, 0, 0)); + join.mintPending(guid, 0, 0); assertEq(dai.balanceOf(address(this)), 225_000 ether); assertEq(_pending(guid), 25_000 ether); @@ -533,10 +499,11 @@ contract TeleportJoinTest is DSTest { }); join.file("line", "l2network", 200_000 ether); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); join.file("line", "l2network", 225_000 ether); - assertTrue(!_tryMintPending(guid, 0, 0)); + vm.expectRevert("TeleportJoin/not-receiver-nor-operator"); + join.mintPending(guid, 0, 0); } function testSettle() public { @@ -570,7 +537,7 @@ contract TeleportJoinTest is DSTest { timestamp: uint48(block.timestamp) }); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); assertEq(dai.balanceOf(address(123)), 250_000 ether); assertEq(_ink(), 150_000 ether); @@ -598,7 +565,7 @@ contract TeleportJoinTest is DSTest { }); join.file("line", "l2network", 100_000 ether); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); assertEq(dai.balanceOf(address(123)), 200_000 ether); assertEq(_pending(guid), 50_000 ether); @@ -630,7 +597,7 @@ contract TeleportJoinTest is DSTest { assertEq(vat.live(), 0); join.file("fees", "l2network", address(new TeleportConstantFee(100 ether, TTL))); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); assertEq(dai.balanceOf(address(123)), 100_000 ether); // Can't pay more than DAI is already in the join assertEq(_pending(guid), 150_000 ether); @@ -651,7 +618,7 @@ contract TeleportJoinTest is DSTest { timestamp: uint48(block.timestamp) }); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); assertEq(join.debt("l2network"), 250_000 ether); assertEq(_ink(), 250_000 ether); @@ -703,7 +670,8 @@ contract TeleportJoinTest is DSTest { nonce: 5, timestamp: uint48(block.timestamp) }); - assertTrue(!_tryRequestMint(guid, 0, 250_001 ether)); // Slightly over the amount + vm.expectRevert(abi.encodeWithSignature("Panic(uint256)", 0x11)); + join.requestMint(guid, 0, 250_001 ether); // Slightly over the amount } function testRegisterAndWithdrawPartialPayingOperatorFee() public { @@ -718,7 +686,7 @@ contract TeleportJoinTest is DSTest { }); join.file("line", "l2network", 200_000 ether); - assertTrue(_tryRequestMint(guid, 0, 200 ether)); + join.requestMint(guid, 0, 200 ether); assertEq(dai.balanceOf(address(this)), 200 ether); assertEq(dai.balanceOf(address(123)), 199_800 ether); @@ -728,7 +696,7 @@ contract TeleportJoinTest is DSTest { assertEq(_art(), 200_000 ether); join.file("line", "l2network", 250_000 ether); - assertTrue(_tryMintPending(guid, 0, 5 ether)); + join.mintPending(guid, 0, 5 ether); assertEq(dai.balanceOf(address(this)), 205 ether); assertEq(dai.balanceOf(address(123)), 249_795 ether); @@ -749,7 +717,7 @@ contract TeleportJoinTest is DSTest { }); assertEq(dai.balanceOf(address(this)), 0); join.file("fees", "l2network", address(new TeleportConstantFee(1000 ether, TTL))); - assertTrue(_tryRequestMint(guid, 40 ether / 10000, 249 ether)); + join.requestMint(guid, 40 ether / 10000, 249 ether); assertEq(dai.balanceOf(address(this)), 249 ether); assertEq(vat.dai(vow), 1000 * RAD); assertEq(dai.balanceOf(address(123)), 248_751 ether); @@ -769,7 +737,8 @@ contract TeleportJoinTest is DSTest { timestamp: uint48(block.timestamp) }); join.file("fees", "l2network", address(new TeleportConstantFee(1000 ether, TTL))); - assertTrue(!_tryRequestMint(guid, 40 ether / 10000, 249_001 ether)); // Too many fees + vm.expectRevert(abi.encodeWithSignature("Panic(uint256)", 0x11)); + join.requestMint(guid, 40 ether / 10000, 249_001 ether); // Too many fees } function testTotalDebtSeveralDomains() public { @@ -792,7 +761,7 @@ contract TeleportJoinTest is DSTest { nonce: 5, timestamp: uint48(block.timestamp) }); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); guid = TeleportGUID({ sourceDomain: "l2network_3", @@ -803,7 +772,7 @@ contract TeleportJoinTest is DSTest { nonce: 5, timestamp: uint48(block.timestamp) }); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); assertEq(join.debt("l2network"), -100_000 ether); assertEq(join.debt("l2network_2"), 150_000 ether); @@ -819,7 +788,7 @@ contract TeleportJoinTest is DSTest { nonce: 5, timestamp: uint48(block.timestamp) }); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); assertEq(join.debt("l2network"), -50_000 ether); assertEq(join.debt("l2network_2"), 150_000 ether); @@ -848,14 +817,14 @@ contract TeleportJoinTest is DSTest { timestamp: uint48(block.timestamp) }); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); assertEq(_ink(), 250_000 ether); assertEq(_art(), 250_000 ether); assertEq(join.cure(), 250_000 * RAD); // Emulate removal of position debt (third party repayment or position being skimmed) - hevm.store( + vm.store( address(vat), bytes32(uint256(keccak256(abi.encode(address(join), keccak256(abi.encode(bytes32(ilk), uint256(2)))))) + 1), bytes32(0) @@ -874,7 +843,7 @@ contract TeleportJoinTest is DSTest { timestamp: uint48(block.timestamp) }); - assertTrue(_tryRequestMint(guid, 0, 0)); + join.requestMint(guid, 0, 0); assertEq(_ink(), 350_000 ether); assertEq(_art(), 100_000 ether); diff --git a/src/test/TeleportLinearFee.t.sol b/src/test/TeleportLinearFee.t.sol index 692e15b..28f0e14 100644 --- a/src/test/TeleportLinearFee.t.sol +++ b/src/test/TeleportLinearFee.t.sol @@ -16,7 +16,7 @@ pragma solidity 0.8.15; -import "ds-test/test.sol"; +import "forge-std/Test.sol"; import "src/TeleportLinearFee.sol"; import "src/TeleportGUID.sol"; @@ -25,9 +25,7 @@ interface Hevm { function warp(uint) external; } -contract TeleportLinearFeeTest is DSTest { - - Hevm internal hevm = Hevm(HEVM_ADDRESS); +contract TeleportLinearFeeTest is Test { uint256 internal fee = 1 ether / 10000; // 1 BPS fee uint256 internal ttl = 8 days; @@ -66,7 +64,7 @@ contract TeleportLinearFeeTest is DSTest { nonce: 5, timestamp: uint48(block.timestamp) }); - hevm.warp(block.timestamp + ttl); + vm.warp(block.timestamp + ttl); assertEq(teleportLinearFee.getFee(guid, 0, 0, 0, 100 ether), 0); } diff --git a/src/test/TeleportOracleAuth.t.sol b/src/test/TeleportOracleAuth.t.sol index 6a8d0a6..cacac33 100644 --- a/src/test/TeleportOracleAuth.t.sol +++ b/src/test/TeleportOracleAuth.t.sol @@ -16,7 +16,7 @@ pragma solidity 0.8.15; -import "ds-test/test.sol"; +import "forge-std/Test.sol"; import "src/TeleportOracleAuth.sol"; @@ -27,9 +27,7 @@ interface Hevm { function sign(uint, bytes32) external returns (uint8, bytes32, bytes32); } -contract TeleportOracleAuthTest is DSTest { - - Hevm hevm = Hevm(HEVM_ADDRESS); +contract TeleportOracleAuthTest is Test { TeleportOracleAuth internal auth; address internal teleportJoin; @@ -38,39 +36,6 @@ contract TeleportOracleAuthTest is DSTest { auth = new TeleportOracleAuth(teleportJoin); } - function _tryRely(address usr) internal returns (bool ok) { - (ok,) = address(auth).call(abi.encodeWithSignature("rely(address)", usr)); - } - - function _tryDeny(address usr) internal returns (bool ok) { - (ok,) = address(auth).call(abi.encodeWithSignature("deny(address)", usr)); - } - - function _tryFile(bytes32 what, uint256 data) internal returns (bool ok) { - (ok,) = address(auth).call(abi.encodeWithSignature("file(bytes32,uint256)", what, data)); - } - - function _tryIsValid(bytes32 signHash, bytes memory signatures, uint256 threshold_) internal returns (bool ok) { - bytes memory res; - (ok, res) = address(auth).call(abi.encodeWithSignature("isValid(bytes32,bytes,uint256)", signHash, signatures, threshold_)); - return ok && abi.decode(res, (bool)); - } - - function _tryRequestMint( - TeleportGUID memory teleportGUID, - bytes memory signatures, - uint256 maxFeePercentage, - uint256 operatorFee - ) internal returns (bool ok) { - (ok,) = address(auth).call(abi.encodeWithSignature( - "requestMint((bytes32,bytes32,bytes32,bytes32,uint128,uint80,uint48),bytes,uint256,uint256)", - teleportGUID, - signatures, - maxFeePercentage, - operatorFee - )); - } - function getSignatures(bytes32 signHash) internal returns (bytes memory signatures, address[] memory signers) { // seeds chosen s.t. corresponding addresses are in ascending order uint8[30] memory seeds = [8,10,6,2,9,15,14,20,7,29,24,13,12,25,16,26,21,22,0,18,17,27,3,28,23,19,4,5,1,11]; @@ -78,8 +43,8 @@ contract TeleportOracleAuthTest is DSTest { signers = new address[](numSigners); for(uint i; i < numSigners; i++) { uint sk = uint256(keccak256(abi.encode(seeds[i]))); - signers[i] = hevm.addr(sk); - (uint8 v, bytes32 r, bytes32 s) = hevm.sign(sk, signHash); + signers[i] = vm.addr(sk); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(sk, signHash); signatures = abi.encodePacked(signatures, r, s, v); } assertEq(signatures.length, numSigners * 65); @@ -92,33 +57,37 @@ contract TeleportOracleAuthTest is DSTest { function testRelyDeny() public { assertEq(auth.wards(address(456)), 0); - assertTrue(_tryRely(address(456))); + auth.rely(address(456)); assertEq(auth.wards(address(456)), 1); - assertTrue(_tryDeny(address(456))); + auth.deny(address(456)); assertEq(auth.wards(address(456)), 0); auth.deny(address(this)); - assertTrue(!_tryRely(address(456))); - assertTrue(!_tryDeny(address(456))); + vm.expectRevert("TeleportOracleAuth/not-authorized"); + auth.rely(address(456)); + vm.expectRevert("TeleportOracleAuth/not-authorized"); + auth.deny(address(456)); } function testFileFailsWhenNotAuthed() public { - assertTrue(_tryFile("threshold", 888)); + auth.file("threshold", 888); auth.deny(address(this)); - assertTrue(!_tryFile("threshold", 888)); + vm.expectRevert("TeleportOracleAuth/not-authorized"); + auth.file("threshold", 888); } function testFileNewThreshold() public { assertEq(auth.threshold(), 0); - assertTrue(_tryFile("threshold", 3)); + auth.file("threshold", 3); assertEq(auth.threshold(), 3); } function testFileInvalidWhat() public { - assertTrue(!_tryFile("meh", 888)); + vm.expectRevert("TeleportOracleAuth/file-unrecognized-param"); + auth.file("meh", 888); } function testAddRemoveSigners() public { @@ -145,7 +114,7 @@ contract TeleportOracleAuthTest is DSTest { bytes32 signHash = keccak256("msg"); (bytes memory signatures, address[] memory signers) = getSignatures(signHash); auth.addSigners(signers); - assertTrue(_tryIsValid(signHash, signatures, signers.length)); + assertTrue(auth.isValid(signHash, signatures, signers.length)); } // Since ecrecover silently returns 0 on failure, it's a good idea to make sure @@ -163,14 +132,16 @@ contract TeleportOracleAuthTest is DSTest { signers[0] = address(0); auth.addSigners(signers); - assertTrue(!_tryIsValid(signHash, signatures, signers.length)); + vm.expectRevert("TeleportOracleAuth/bad-sig-order"); + auth.isValid(signHash, signatures, signers.length); } function test_isValid_notEnoughSig() public { bytes32 signHash = keccak256("msg"); (bytes memory signatures, address[] memory signers) = getSignatures(signHash); auth.addSigners(signers); - assertTrue(!_tryIsValid(signHash, signatures, signers.length + 1)); + vm.expectRevert("TeleportOracleAuth/not-enough-sig"); + auth.isValid(signHash, signatures, signers.length + 1); } function test_isValid_badSig() public { @@ -178,7 +149,8 @@ contract TeleportOracleAuthTest is DSTest { (bytes memory signatures, address[] memory signers) = getSignatures(signHash); auth.addSigners(signers); signatures[0] = bytes1(uint8((uint256(uint8(signatures[0])) + 1) % 256)); - assertTrue(!_tryIsValid(signHash, signatures, signers.length)); + vm.expectRevert("TeleportOracleAuth/bad-sig-order"); + auth.isValid(signHash, signatures, signers.length); } function test_mintByOperator() public { @@ -195,7 +167,7 @@ contract TeleportOracleAuthTest is DSTest { uint maxFee = 0; - assertTrue(_tryRequestMint(guid, signatures, maxFee, 0)); + auth.requestMint(guid, signatures, maxFee, 0); } function test_mintByOperatorNotReceiver() public { @@ -212,7 +184,7 @@ contract TeleportOracleAuthTest is DSTest { uint maxFee = 0; - assertTrue(_tryRequestMint(guid, signatures, maxFee, 0)); + auth.requestMint(guid, signatures, maxFee, 0); } function test_mintByReceiver() public { @@ -229,7 +201,7 @@ contract TeleportOracleAuthTest is DSTest { uint maxFee = 0; - assertTrue(_tryRequestMint(guid, signatures, maxFee, 0)); + auth.requestMint(guid, signatures, maxFee, 0); } function test_mint_notOperatorNorReceiver() public { @@ -246,7 +218,8 @@ contract TeleportOracleAuthTest is DSTest { uint maxFee = 0; - assertTrue(!_tryRequestMint(guid, signatures, maxFee, 0)); + vm.expectRevert("TeleportOracleAuth/not-receiver-nor-operator"); + auth.requestMint(guid, signatures, maxFee, 0); } } diff --git a/src/test/TeleportRouter.t.sol b/src/test/TeleportRouter.t.sol index c843c54..c437c5d 100644 --- a/src/test/TeleportRouter.t.sol +++ b/src/test/TeleportRouter.t.sol @@ -16,14 +16,14 @@ pragma solidity 0.8.15; -import "ds-test/test.sol"; +import "forge-std/Test.sol"; import "src/TeleportRouter.sol"; import "./mocks/GatewayMock.sol"; import "./mocks/DaiMock.sol"; -contract TeleportRouterTest is DSTest { +contract TeleportRouterTest is Test { TeleportRouter internal router; address internal dai; @@ -39,37 +39,6 @@ contract TeleportRouterTest is DSTest { router = new TeleportRouter(dai, domain, parentDomain); } - function _tryRely(address usr) internal returns (bool ok) { - (ok,) = address(router).call(abi.encodeWithSignature("rely(address)", usr)); - } - - function _tryDeny(address usr) internal returns (bool ok) { - (ok,) = address(router).call(abi.encodeWithSignature("deny(address)", usr)); - } - - function _tryFile(bytes32 what, bytes32 _domain, address data) internal returns (bool ok) { - (ok,) = address(router).call(abi.encodeWithSignature("file(bytes32,bytes32,address)", what, _domain, data)); - } - - function _tryFile(bytes32 what, uint256 data) internal returns (bool ok) { - (ok,) = address(router).call(abi.encodeWithSignature("file(bytes32,uint256)", what, data)); - } - - function _tryRegisterMint( - TeleportGUID memory teleportGUID - ) internal returns (bool ok) { - (ok,) = address(router).call(abi.encodeWithSignature( - "registerMint((bytes32,bytes32,bytes32,bytes32,uint128,uint80,uint48))", - teleportGUID - )); - } - function _tryFlush(bytes32 targetDomain) internal returns (bool ok) { - (ok,) = address(router).call(abi.encodeWithSignature("flush(bytes32)", targetDomain)); - } - function _trySettle(bytes32 sourceDomain, bytes32 targetDomain, uint256 batchedDaiToFlush) internal returns (bool ok) { - (ok,) = address(router).call(abi.encodeWithSignature("settle(bytes32,bytes32,uint256)", sourceDomain, targetDomain, batchedDaiToFlush)); - } - function testConstructor() public { assertEq(address(router.dai()), dai); assertEq(router.domain(), domain); @@ -79,15 +48,17 @@ contract TeleportRouterTest is DSTest { function testRelyDeny() public { assertEq(router.wards(address(456)), 0); - assertTrue(_tryRely(address(456))); + router.rely(address(456)); assertEq(router.wards(address(456)), 1); - assertTrue(_tryDeny(address(456))); + router.deny(address(456)); assertEq(router.wards(address(456)), 0); router.deny(address(this)); - assertTrue(!_tryRely(address(456))); - assertTrue(!_tryDeny(address(456))); + vm.expectRevert("TeleportRouter/not-authorized"); + router.rely(address(456)); + vm.expectRevert("TeleportRouter/not-authorized"); + router.deny(address(456)); } function testFileNewDomains() public { @@ -96,7 +67,7 @@ contract TeleportRouterTest is DSTest { assertEq(router.gateways(domain1), address(0)); assertEq(router.numDomains(), 0); - assertTrue(_tryFile("gateway", domain1, gateway1)); + router.file("gateway", domain1, gateway1); assertEq(router.gateways(domain1), gateway1); assertEq(router.numDomains(), 1); @@ -106,7 +77,7 @@ contract TeleportRouterTest is DSTest { address gateway2 = address(222); assertEq(router.gateways(domain2), address(0)); - assertTrue(_tryFile("gateway", domain2, gateway2)); + router.file("gateway", domain2, gateway2); assertEq(router.gateways(domain2), gateway2); assertEq(router.numDomains(), 2); @@ -117,13 +88,13 @@ contract TeleportRouterTest is DSTest { function testFileNewGatewayForExistingDomain() public { bytes32 domain1 = "dom"; address gateway1 = address(111); - assertTrue(_tryFile("gateway", domain1, gateway1)); + router.file("gateway", domain1, gateway1); assertEq(router.gateways(domain1), gateway1); assertEq(router.numDomains(), 1); assertEq(router.domainAt(0), domain1); address gateway2 = address(222); - assertTrue(_tryFile("gateway", domain1, gateway2)); + router.file("gateway", domain1, gateway2); assertEq(router.gateways(domain1), gateway2); assertEq(router.numDomains(), 1); @@ -133,13 +104,13 @@ contract TeleportRouterTest is DSTest { function testFileRemoveLastDomain() public { bytes32 domain1 = "dom"; address gateway = address(111); - assertTrue(_tryFile("gateway", domain1, gateway)); + router.file("gateway", domain1, gateway); assertEq(router.gateways(domain1), gateway); assertEq(router.numDomains(), 1); assertEq(router.domainAt(0), domain1); // Remove last domain1 - assertTrue(_tryFile("gateway", domain1, address(0))); + router.file("gateway", domain1, address(0)); assertEq(router.gateways(domain1), address(0)); assertTrue(!router.hasDomain(domain1)); @@ -152,8 +123,8 @@ contract TeleportRouterTest is DSTest { bytes32 domain2 = "dom2"; address gateway1 = address(111); address gateway2 = address(222); - assertTrue(_tryFile("gateway", domain1, gateway1)); - assertTrue(_tryFile("gateway", domain2, gateway2)); + router.file("gateway", domain1, gateway1); + router.file("gateway", domain2, gateway2); assertEq(router.gateways(domain1), gateway1); assertEq(router.gateways(domain2), gateway2); assertEq(router.numDomains(), 2); @@ -161,7 +132,7 @@ contract TeleportRouterTest is DSTest { assertEq(router.domainAt(1), domain2); // Remove first domain - assertTrue(_tryFile("gateway", domain1, address(0))); + router.file("gateway", domain1, address(0)); assertEq(router.gateways(domain1), address(0)); assertEq(router.gateways(domain2), gateway2); @@ -169,7 +140,7 @@ contract TeleportRouterTest is DSTest { assertEq(router.domainAt(0), domain2); // Re-add removed domain - assertTrue(_tryFile("gateway", domain1, gateway1)); + router.file("gateway", domain1, gateway1); assertEq(router.gateways(domain1), gateway1); assertEq(router.gateways(domain2), gateway2); @@ -182,8 +153,8 @@ contract TeleportRouterTest is DSTest { bytes32 domain1 = "dom1"; bytes32 domain2 = "dom2"; address gateway1 = address(111); - assertTrue(_tryFile("gateway", domain1, gateway1)); - assertTrue(_tryFile("gateway", domain2, gateway1)); + router.file("gateway", domain1, gateway1); + router.file("gateway", domain2, gateway1); assertEq(router.gateways(domain1), gateway1); assertEq(router.gateways(domain2), gateway1); assertEq(router.numDomains(), 2); @@ -195,10 +166,10 @@ contract TeleportRouterTest is DSTest { bytes32 domain1 = "dom1"; bytes32 domain2 = "dom2"; address gateway1 = address(111); - assertTrue(_tryFile("gateway", domain1, gateway1)); - assertTrue(_tryFile("gateway", domain2, gateway1)); + router.file("gateway", domain1, gateway1); + router.file("gateway", domain2, gateway1); - assertTrue(_tryFile("gateway", domain2, address(0))); + router.file("gateway", domain2, address(0)); assertEq(router.gateways(domain1), gateway1); assertEq(router.gateways(domain2), address(0)); @@ -210,11 +181,11 @@ contract TeleportRouterTest is DSTest { bytes32 domain1 = "dom1"; bytes32 domain2 = "dom2"; address gateway1 = address(111); - assertTrue(_tryFile("gateway", domain1, gateway1)); - assertTrue(_tryFile("gateway", domain2, gateway1)); + router.file("gateway", domain1, gateway1); + router.file("gateway", domain2, gateway1); - assertTrue(_tryFile("gateway", domain1, address(0))); - assertTrue(_tryFile("gateway", domain2, address(0))); + router.file("gateway", domain1, address(0)); + router.file("gateway", domain2, address(0)); assertEq(router.gateways(domain1), address(0)); assertEq(router.gateways(domain2), address(0)); @@ -226,10 +197,10 @@ contract TeleportRouterTest is DSTest { bytes32 domain2 = "dom2"; address gateway1 = address(111); address gateway2 = address(222); - assertTrue(_tryFile("gateway", domain1, gateway1)); - assertTrue(_tryFile("gateway", domain2, gateway1)); + router.file("gateway", domain1, gateway1); + router.file("gateway", domain2, gateway1); - assertTrue(_tryFile("gateway", domain2, gateway2)); + router.file("gateway", domain2, gateway2); assertEq(router.gateways(domain1), gateway1); assertEq(router.gateways(domain2), gateway2); @@ -243,11 +214,11 @@ contract TeleportRouterTest is DSTest { bytes32 domain2 = "dom2"; address gateway1 = address(111); address gateway2 = address(222); - assertTrue(_tryFile("gateway", domain1, gateway1)); - assertTrue(_tryFile("gateway", domain2, gateway1)); + router.file("gateway", domain1, gateway1); + router.file("gateway", domain2, gateway1); - assertTrue(_tryFile("gateway", domain2, gateway2)); - assertTrue(_tryFile("gateway", domain1, address(0))); + router.file("gateway", domain2, gateway2); + router.file("gateway", domain1, address(0)); assertEq(router.gateways(domain1), address(0)); assertEq(router.gateways(domain2), gateway2); @@ -257,18 +228,21 @@ contract TeleportRouterTest is DSTest { function testFile() public { assertEq(router.fdust(), 0); - assertTrue(_tryFile("fdust", 888)); + router.file("fdust", 888); assertEq(router.fdust(), 888); } function testFileInvalidWhat() public { - assertTrue(!_tryFile("meh", "aaa", address(888))); + vm.expectRevert("TeleportRouter/file-unrecognized-param"); + router.file("meh", "aaa", address(888)); } function testFileFailsWhenNotAuthed() public { router.deny(address(this)); - assertTrue(!_tryFile("gateway", "dom", address(888))); - assertTrue(!_tryFile("fdust", 1)); + vm.expectRevert("TeleportRouter/not-authorized"); + router.file("gateway", "dom", address(888)); + vm.expectRevert("TeleportRouter/not-authorized"); + router.file("fdust", 1); } function testRegisterMintFromNotGateway() public { @@ -283,7 +257,8 @@ contract TeleportRouterTest is DSTest { }); router.file("gateway", "l2network", address(555)); - assertTrue(!_tryRegisterMint(guid)); + vm.expectRevert("TeleportRouter/sender-not-gateway"); + router.registerMint(guid); } function testRegisterMintTargetingActualDomain() public { @@ -299,7 +274,7 @@ contract TeleportRouterTest is DSTest { router.file("gateway", "l2network", address(this)); router.file("gateway", domain, teleportJoin); - assertTrue(_tryRegisterMint(guid)); + router.registerMint(guid); } function testRegisterMintTargetingSubDomain() public { @@ -315,7 +290,7 @@ contract TeleportRouterTest is DSTest { router.file("gateway", "l2network", address(this)); router.file("gateway", "another-l2network", address(new GatewayMock())); - assertTrue(_tryRegisterMint(guid)); + router.registerMint(guid); } function testRegisterMintTargetingInvalidDomain() public { @@ -330,7 +305,8 @@ contract TeleportRouterTest is DSTest { }); router.file("gateway", "l2network", address(this)); - assertTrue(!_tryRegisterMint(guid)); + vm.expectRevert("TeleportRouter/unsupported-target-domain"); + router.registerMint(guid); } function testRegisterMintFromParentGateway() public { @@ -346,7 +322,7 @@ contract TeleportRouterTest is DSTest { router.file("gateway", parentDomain, address(this)); router.file("gateway", "another-l2network", address(new GatewayMock())); - assertTrue(_tryRegisterMint(guid)); + router.registerMint(guid); } function testSettleFromNotGateway() public { @@ -354,7 +330,8 @@ contract TeleportRouterTest is DSTest { DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); - assertTrue(!_trySettle("l2network", domain, 100 ether)); + vm.expectRevert("TeleportRouter/sender-not-gateway"); + router.settle("l2network", domain, 100 ether); } function testSettleTargetingActualDomain() public { @@ -363,7 +340,7 @@ contract TeleportRouterTest is DSTest { DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); - assertTrue(_trySettle("l2network", domain, 100 ether)); + router.settle("l2network", domain, 100 ether); } function testSettleTargetingSubDomain() public { @@ -372,7 +349,7 @@ contract TeleportRouterTest is DSTest { DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); - assertTrue(_trySettle("l2network", "another-l2network", 100 ether)); + router.settle("l2network", "another-l2network", 100 ether); } function testSettleFromParentGateway() public { @@ -381,13 +358,14 @@ contract TeleportRouterTest is DSTest { DaiMock(dai).mint(address(this), 100 ether); DaiMock(dai).approve(address(router), 100 ether); - assertTrue(_trySettle("l2network", "another-l2network", 100 ether)); + router.settle("l2network", "another-l2network", 100 ether); } function testSettleTargetingInvalidDomain() public { router.file("gateway", "l2network", address(this)); - assertTrue(!_trySettle("l2network", "invalid-network", 100 ether)); + vm.expectRevert("TeleportRouter/unsupported-target-domain"); + router.settle("l2network", "invalid-network", 100 ether); } function testInitiateTeleport() public { @@ -420,7 +398,7 @@ contract TeleportRouterTest is DSTest { assertEq(DaiMock(dai).balanceOf(address(router)), 100_000 ether); assertEq(DaiMock(dai).balanceOf(parentGateway), 0); - assertTrue(_tryFlush(parentDomain)); + router.flush(parentDomain); assertEq(router.batches(parentDomain), 0); assertEq(DaiMock(dai).balanceOf(address(router)), 0); @@ -439,6 +417,7 @@ contract TeleportRouterTest is DSTest { assertEq(DaiMock(dai).balanceOf(parentGateway), 0); router.file("fdust", 200_000 ether); - assertTrue(!_tryFlush(parentDomain)); + vm.expectRevert("TeleportRouter/flush-dust"); + router.flush(parentDomain); } } diff --git a/src/test/integration/TeleportJoin.t.sol b/src/test/integration/TeleportJoin.t.sol index 10d8fb8..99c9fe3 100644 --- a/src/test/integration/TeleportJoin.t.sol +++ b/src/test/integration/TeleportJoin.t.sol @@ -16,7 +16,7 @@ pragma solidity 0.8.15; -import "ds-test/test.sol"; +import "forge-std/Test.sol"; import {TeleportJoin} from "src/TeleportJoin.sol"; import "src/TeleportGUID.sol"; @@ -62,10 +62,7 @@ interface TokenLike { function approve(address, uint256) external returns (bool); } -contract TeleportJoinIntegrationTest is DSTest { - - Hevm internal hevm = Hevm(HEVM_ADDRESS); - +contract TeleportJoinIntegrationTest is Test { bytes32 constant internal ILK = "TELEPORT-ETHEREUM-MASTER-1"; bytes32 constant internal MASTER_DOMAIN = "ETHEREUM-MASTER-1"; bytes32 constant internal SLAVE_DOMAIN = "L2NETWORK-SLAVE-1"; @@ -84,7 +81,7 @@ contract TeleportJoinIntegrationTest is DSTest { uint256 internal constant TTL = 8 days; function getAuthFor(address auth) internal { - hevm.store( + vm.store( auth, keccak256(abi.encode(address(this), 0)), bytes32(uint256(1)) @@ -157,8 +154,8 @@ contract TeleportJoinIntegrationTest is DSTest { // thaw the end uint256 vatDebt = vat.debt(); - hevm.warp(block.timestamp + end.wait()); - hevm.store(address(vat), keccak256(abi.encode(vow, 5)), bytes32(0)); // emulate clearing of vow dai + vm.warp(block.timestamp + end.wait()); + vm.store(address(vat), keccak256(abi.encode(vow, 5)), bytes32(0)); // emulate clearing of vow dai assertEq(vat.dai(vow), 0); end.thaw(); diff --git a/test.sh b/test.sh index 68b11b2..25d6e8a 100755 --- a/test.sh +++ b/test.sh @@ -1,17 +1,13 @@ #!/usr/bin/env bash set -e -[[ "$ETH_RPC_URL" && "$(seth chain)" == "ethlive" ]] || { echo "Please set a mainnet ETH_RPC_URL"; exit 1; } +[[ "$ETH_RPC_URL" && "$(cast chain)" == "ethlive" ]] || { echo "Please set a mainnet ETH_RPC_URL"; exit 1; } -export DAPP_BUILD_OPTIMIZE=1 -export DAPP_BUILD_OPTIMIZE_RUNS=200 +export FOUNDRY_OPTIMIZER=true +export FOUNDRY_OPTIMIZER_RUNS=200 -if [[ -z "$1" && -z "$2" ]]; then - dapp --use solc:0.8.15 test --rpc-url="$ETH_RPC_URL" --fuzz-runs 1 -vv -elif [[ -z "$2" ]]; then - dapp --use solc:0.8.15 test --rpc-url="$ETH_RPC_URL" --match "$1" --fuzz-runs 1 -vv -elif [[ -z "$1" ]]; then - dapp --use solc:0.8.15 test --rpc-url="$ETH_RPC_URL" --fuzz-runs "$2" -vv +if [[ -z "$1" ]]; then + forge test --use solc:0.8.15 --fork-url "$ETH_RPC_URL" -vv else - dapp --use solc:0.8.15 test --rpc-url="$ETH_RPC_URL" --match "$1" --fuzz-runs "$2" -vv + forge test --use solc:0.8.15 --fork-url "$ETH_RPC_URL" --match "$1" -vv fi