diff --git a/src/TeleportConstantFee.sol b/src/TeleportConstantFee.sol
new file mode 100644
index 0000000..2c6e26c
--- /dev/null
+++ b/src/TeleportConstantFee.sol
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// Copyright (C) 2021 Dai Foundation
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+pragma solidity 0.8.14;
+
+import {TeleportFees} from "./TeleportFees.sol";
+import {TeleportGUID} from "./TeleportGUID.sol";
+
+contract TeleportConstantFee is TeleportFees {
+ uint256 immutable public fee;
+ uint256 immutable public ttl;
+
+ /**
+ * @param _fee Constant fee in WAD
+ * @param _ttl Time in seconds to finalize flush (not teleport)
+ **/
+ constructor(uint256 _fee, uint256 _ttl) {
+ fee = _fee;
+ ttl = _ttl;
+ }
+
+ function getFee(TeleportGUID calldata guid, uint256, int256, uint256, uint256 amtToTake) override external view returns (uint256) {
+ // is slow withdrawal?
+ if (block.timestamp >= uint256(guid.timestamp) + ttl) {
+ return 0;
+ }
+
+ // is empty teleport?
+ if (guid.amount == 0) {
+ return 0;
+ }
+
+ return fee * amtToTake / guid.amount;
+ }
+}
diff --git a/src/TeleportFees.sol b/src/TeleportFees.sol
new file mode 100644
index 0000000..6526541
--- /dev/null
+++ b/src/TeleportFees.sol
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// Copyright (C) 2021 Dai Foundation
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+pragma solidity 0.8.14;
+
+import "./TeleportGUID.sol";
+
+// Calculate fees for a given Teleport GUID
+interface TeleportFees {
+ /**
+ * @dev Return fee for particular teleport. It should return 0 for teleports that are being slow withdrawn.
+ * note: We define slow withdrawal as teleport older than x. x has to be enough to finalize flush (not teleport itself).
+ * @param teleportGUID Struct which contains the whole teleport data
+ * @param line Debt ceiling
+ * @param debt Current debt
+ * @param pending Amount left to withdraw
+ * @param amtToTake Amount to take. Can be less or equal to teleportGUID.amount b/c of debt ceiling or because it is pending
+ * @return fees Fee amount [WAD]
+ **/
+ function getFee(
+ TeleportGUID calldata teleportGUID, uint256 line, int256 debt, uint256 pending, uint256 amtToTake
+ ) external view returns (uint256 fees);
+}
diff --git a/src/TeleportGUID.sol b/src/TeleportGUID.sol
new file mode 100644
index 0000000..9848304
--- /dev/null
+++ b/src/TeleportGUID.sol
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// Copyright (C) 2021 Dai Foundation
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+pragma solidity 0.8.14;
+
+// Standard Maker Teleport GUID
+struct TeleportGUID {
+ bytes32 sourceDomain;
+ bytes32 targetDomain;
+ bytes32 receiver;
+ bytes32 operator;
+ uint128 amount;
+ uint80 nonce;
+ uint48 timestamp;
+}
+
+// solhint-disable-next-line func-visibility
+function bytes32ToAddress(bytes32 addr) pure returns (address) {
+ return address(uint160(uint256(addr)));
+}
+
+// solhint-disable-next-line func-visibility
+function addressToBytes32(address addr) pure returns (bytes32) {
+ return bytes32(uint256(uint160(addr)));
+}
+
+// solhint-disable-next-line func-visibility
+function getGUIDHash(TeleportGUID memory teleportGUID) pure returns (bytes32 guidHash) {
+ guidHash = keccak256(abi.encode(
+ teleportGUID.sourceDomain,
+ teleportGUID.targetDomain,
+ teleportGUID.receiver,
+ teleportGUID.operator,
+ teleportGUID.amount,
+ teleportGUID.nonce,
+ teleportGUID.timestamp
+ ));
+}
diff --git a/src/TeleportJoin.sol b/src/TeleportJoin.sol
new file mode 100644
index 0000000..f607c02
--- /dev/null
+++ b/src/TeleportJoin.sol
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// Copyright (C) 2021 Dai Foundation
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+pragma solidity 0.8.14;
+
+import "./TeleportGUID.sol";
+
+interface VatLike {
+ function dai(address) external view returns (uint256);
+ function live() external view returns (uint256);
+ function urns(bytes32, address) external view returns (uint256, uint256);
+ function frob(bytes32, address, address, address, int256, int256) external;
+ function hope(address) external;
+ function move(address, address, uint256) external;
+ function nope(address) external;
+ function slip(bytes32, address, int256) external;
+}
+
+interface DaiJoinLike {
+ function dai() external view returns (TokenLike);
+ function exit(address, uint256) external;
+ function join(address, uint256) external;
+}
+
+interface TokenLike {
+ function approve(address, uint256) external returns (bool);
+}
+
+interface FeesLike {
+ function getFee(TeleportGUID calldata, uint256, int256, uint256, uint256) external view returns (uint256);
+}
+
+// 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
+
+ address public vow;
+
+ uint256 internal art; // We need to preserve the last art value before the position being skimmed (End)
+
+ VatLike immutable public vat;
+ DaiJoinLike immutable public daiJoin;
+ bytes32 immutable public ilk;
+ bytes32 immutable public domain;
+
+ uint256 constant public WAD = 10 ** 18;
+ uint256 constant public RAY = 10 ** 27;
+
+ event Rely(address indexed usr);
+ event Deny(address indexed usr);
+ event File(bytes32 indexed what, address 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);
+
+ struct TeleportStatus {
+ bool blessed;
+ uint248 pending;
+ }
+
+ constructor(address vat_, address daiJoin_, bytes32 ilk_, bytes32 domain_) {
+ wards[msg.sender] = 1;
+ emit Rely(msg.sender);
+ vat = VatLike(vat_);
+ daiJoin = DaiJoinLike(daiJoin_);
+ vat.hope(daiJoin_);
+ daiJoin.dai().approve(daiJoin_, type(uint256).max);
+ ilk = ilk_;
+ domain = domain_;
+ }
+
+ function _min(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ z = x <= y ? x : y;
+ }
+
+ modifier auth {
+ require(wards[msg.sender] == 1, "TeleportJoin/not-authorized");
+ _;
+ }
+
+ function rely(address usr) external auth {
+ wards[usr] = 1;
+ emit Rely(usr);
+ }
+
+ function deny(address usr) external auth {
+ wards[usr] = 0;
+ emit Deny(usr);
+ }
+
+ function file(bytes32 what, address data) external auth {
+ if (what == "vow") {
+ vow = 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;
+ } else {
+ revert("TeleportJoin/file-unrecognized-param");
+ }
+ emit File(what, domain_, data);
+ }
+
+ function file(bytes32 what, bytes32 domain_, uint256 data) external auth {
+ if (what == "line") {
+ require(data <= 2 ** 255 - 1, "TeleportJoin/not-allowed-bigger-int256");
+ line[domain_] = data;
+ } else {
+ revert("TeleportJoin/file-unrecognized-param");
+ }
+ emit File(what, domain_, data);
+ }
+
+ /**
+ * @dev External view function to get the total debt used by this contract [RAD]
+ **/
+ function cure() external view returns (uint256 cure_) {
+ cure_ = art * RAY;
+ }
+
+ /**
+ * @dev Internal function that executes the mint after a teleport is registered
+ * @param teleportGUID Struct which contains the whole teleport data
+ * @param hashGUID Hash of the prev struct
+ * @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 _mint(
+ TeleportGUID calldata teleportGUID,
+ bytes32 hashGUID,
+ uint256 maxFeePercentage,
+ uint256 operatorFee
+ ) internal returns (uint256 postFeeAmount, uint256 totalFee) {
+ require(teleportGUID.targetDomain == domain, "TeleportJoin/incorrect-domain");
+
+ bool vatLive = vat.live() == 1;
+
+ uint256 line_ = vatLive ? line[teleportGUID.sourceDomain] : 0;
+
+ int256 debt_ = debt[teleportGUID.sourceDomain];
+
+ // Stop execution if there isn't anything available to withdraw
+ uint248 pending = teleports[hashGUID].pending;
+ if (int256(line_) <= debt_ || pending == 0) {
+ emit Mint(hashGUID, teleportGUID, 0, maxFeePercentage, operatorFee, msg.sender);
+ return (0, 0);
+ }
+
+ uint256 amtToTake = _min(
+ pending,
+ uint256(int256(line_) - debt_)
+ );
+
+ uint256 fee = vatLive ? FeesLike(fees[teleportGUID.sourceDomain]).getFee(teleportGUID, line_, debt_, pending, amtToTake) : 0;
+ require(fee <= maxFeePercentage * amtToTake / WAD, "TeleportJoin/max-fee-exceed");
+
+ // No need of overflow check here as amtToTake is bounded by teleports[hashGUID].pending
+ // which is already a uint248. Also int256 >> uint248. Then both castings are safe.
+ debt[teleportGUID.sourceDomain] += int256(amtToTake);
+ teleports[hashGUID].pending -= uint248(amtToTake);
+
+ if (debt_ >= 0 || uint256(-debt_) < amtToTake) {
+ uint256 amtToGenerate = debt_ < 0
+ ? uint256(int256(amtToTake) + debt_) // amtToTake - |debt_|
+ : amtToTake;
+ // amtToGenerate doesn't need overflow check as it is bounded by amtToTake
+ vat.slip(ilk, address(this), int256(amtToGenerate));
+ vat.frob(ilk, address(this), address(this), address(this), int256(amtToGenerate), int256(amtToGenerate));
+ // Query the actual value as someone might have repaid debt without going through settle (if vat.live == 0 prev frob will revert)
+ (, art) = vat.urns(ilk, address(this));
+ }
+ totalFee = fee + operatorFee;
+ postFeeAmount = amtToTake - totalFee;
+ daiJoin.exit(bytes32ToAddress(teleportGUID.receiver), postFeeAmount);
+
+ if (fee > 0) {
+ vat.move(address(this), vow, fee * RAY);
+ }
+ if (operatorFee > 0) {
+ daiJoin.exit(bytes32ToAddress(teleportGUID.operator), operatorFee);
+ }
+
+ emit Mint(hashGUID, teleportGUID, amtToTake, maxFeePercentage, operatorFee, msg.sender);
+ }
+
+ /**
+ * @dev External authed function that registers the teleport and executes the mint after
+ * @param teleportGUID Struct which contains the whole teleport data
+ * @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 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);
+ (postFeeAmount, totalFee) = _mint(teleportGUID, hashGUID, maxFeePercentage, operatorFee);
+ }
+
+ /**
+ * @dev External function that executes the mint of any pending and available amount (only callable by operator or receiver)
+ * @param teleportGUID Struct which contains the whole teleport data
+ * @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 mintPending(
+ TeleportGUID calldata teleportGUID,
+ uint256 maxFeePercentage,
+ uint256 operatorFee
+ ) external returns (uint256 postFeeAmount, uint256 totalFee) {
+ require(bytes32ToAddress(teleportGUID.receiver) == msg.sender ||
+ bytes32ToAddress(teleportGUID.operator) == msg.sender, "TeleportJoin/not-receiver-nor-operator");
+ (postFeeAmount, totalFee) = _mint(teleportGUID, getGUIDHash(teleportGUID), maxFeePercentage, operatorFee);
+ }
+
+ /**
+ * @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 batchedDaiToFlush Amount of DAI that is being processed for repayment
+ **/
+ function settle(bytes32 sourceDomain, uint256 batchedDaiToFlush) external {
+ require(batchedDaiToFlush <= 2 ** 255, "TeleportJoin/overflow");
+ daiJoin.join(address(this), batchedDaiToFlush);
+ if (vat.live() == 1) {
+ (, uint256 art_) = vat.urns(ilk, address(this)); // rate == RAY => normalized debt == actual debt
+ uint256 amtToPayBack = _min(batchedDaiToFlush, 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);
+ }
+}
diff --git a/src/TeleportOracleAuth.sol b/src/TeleportOracleAuth.sol
new file mode 100644
index 0000000..40911f0
--- /dev/null
+++ b/src/TeleportOracleAuth.sol
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// Copyright (C) 2021 Dai Foundation
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+pragma solidity 0.8.14;
+
+import "./TeleportGUID.sol";
+
+interface TeleportJoinLike {
+ function requestMint(
+ TeleportGUID calldata teleportGUID,
+ uint256 maxFeePercentage,
+ uint256 operatorFee
+ ) external returns (uint256 postFeeAmount, uint256 totalFee);
+}
+
+// TeleportOracleAuth provides user authentication for TeleportJoin, by means of Maker Oracle Attestations
+contract TeleportOracleAuth {
+
+ mapping (address => uint256) public wards; // Auth
+ mapping (address => uint256) public signers; // Oracle feeds
+
+ TeleportJoinLike immutable public teleportJoin;
+
+ uint256 public threshold;
+
+ event Rely(address indexed usr);
+ event Deny(address indexed usr);
+ event File(bytes32 indexed what, uint256 data);
+ event SignersAdded(address[] signers);
+ event SignersRemoved(address[] signers);
+
+ modifier auth {
+ require(wards[msg.sender] == 1, "TeleportOracleAuth/not-authorized");
+ _;
+ }
+
+ constructor(address teleportJoin_) {
+ wards[msg.sender] = 1;
+ emit Rely(msg.sender);
+ teleportJoin = TeleportJoinLike(teleportJoin_);
+ }
+
+ function rely(address usr) external auth {
+ wards[usr] = 1;
+ emit Rely(usr);
+ }
+
+ function deny(address usr) external auth {
+ wards[usr] = 0;
+ emit Deny(usr);
+ }
+
+ function file(bytes32 what, uint256 data) external auth {
+ if (what == "threshold") {
+ threshold = data;
+ } else {
+ revert("TeleportOracleAuth/file-unrecognized-param");
+ }
+ emit File(what, data);
+ }
+
+ function addSigners(address[] calldata signers_) external auth {
+ for(uint i; i < signers_.length; i++) {
+ signers[signers_[i]] = 1;
+ }
+ emit SignersAdded(signers_);
+ }
+
+ function removeSigners(address[] calldata signers_) external auth {
+ for(uint i; i < signers_.length; i++) {
+ signers[signers_[i]] = 0;
+ }
+ emit SignersRemoved(signers_);
+ }
+
+ /**
+ * @notice Verify oracle signatures and call TeleportJoin to mint DAI if the signatures are valid
+ * (only callable by teleport's operator or receiver)
+ * @param teleportGUID The teleport GUID to register
+ * @param signatures The byte array of concatenated signatures ordered by increasing signer addresses.
+ * Each signature is {bytes32 r}{bytes32 s}{uint8 v}
+ * @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,
+ bytes calldata signatures,
+ uint256 maxFeePercentage,
+ uint256 operatorFee
+ ) external returns (uint256 postFeeAmount, uint256 totalFee) {
+ require(bytes32ToAddress(teleportGUID.receiver) == msg.sender ||
+ bytes32ToAddress(teleportGUID.operator) == msg.sender, "TeleportOracleAuth/not-receiver-nor-operator");
+ require(isValid(getSignHash(teleportGUID), signatures, threshold), "TeleportOracleAuth/not-enough-valid-sig");
+ (postFeeAmount, totalFee) = teleportJoin.requestMint(teleportGUID, maxFeePercentage, operatorFee);
+ }
+
+ /**
+ * @notice Returns true if `signatures` contains at least `threshold_` valid signatures of a given `signHash`
+ * @param signHash The signed message hash
+ * @param signatures The byte array of concatenated signatures ordered by increasing signer addresses.
+ * Each signature is {bytes32 r}{bytes32 s}{uint8 v}
+ * @param threshold_ The minimum number of valid signatures required for the method to return true
+ * @return valid Signature verification result
+ */
+ function isValid(bytes32 signHash, bytes calldata signatures, uint threshold_) public view returns (bool valid) {
+ uint256 count = signatures.length / 65;
+ require(count >= threshold_, "TeleportOracleAuth/not-enough-sig");
+
+ uint8 v;
+ bytes32 r;
+ bytes32 s;
+ uint256 numValid;
+ address lastSigner;
+ for (uint256 i; i < count;) {
+ (v,r,s) = splitSignature(signatures, i);
+ address recovered = ecrecover(signHash, v, r, s);
+ require(recovered > lastSigner, "TeleportOracleAuth/bad-sig-order"); // make sure signers are different
+ lastSigner = recovered;
+ if (signers[recovered] == 1) {
+ unchecked { numValid += 1; }
+ if (numValid >= threshold_) {
+ return true;
+ }
+ }
+ unchecked { i++; }
+ }
+ }
+
+ /**
+ * @notice This has to match what oracles are signing
+ * @param teleportGUID The teleport GUID to calculate hash
+ */
+ function getSignHash(TeleportGUID memory teleportGUID) public pure returns (bytes32 signHash) {
+ signHash = keccak256(abi.encodePacked(
+ "\x19Ethereum Signed Message:\n32",
+ getGUIDHash(teleportGUID)
+ ));
+ }
+
+ /**
+ * @notice Parses the signatures and extract (r, s, v) for a signature at a given index.
+ * @param signatures concatenated signatures. Each signature is {bytes32 r}{bytes32 s}{uint8 v}
+ * @param index which signature to read (0, 1, 2, ...)
+ */
+ function splitSignature(bytes calldata signatures, uint256 index) internal pure returns (uint8 v, bytes32 r, bytes32 s) {
+ // we jump signatures.offset to get the first slot of signatures content
+ // we jump 65 (0x41) per signature
+ // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask
+ uint256 start;
+ // solhint-disable-next-line no-inline-assembly
+ assembly {
+ start := mul(0x41, index)
+ r := calldataload(add(signatures.offset, start))
+ s := calldataload(add(signatures.offset, add(0x20, start)))
+ v := and(calldataload(add(signatures.offset, add(0x21, start))), 0xff)
+ }
+ require(v == 27 || v == 28, "TeleportOracleAuth/bad-v");
+ }
+}
diff --git a/src/TeleportRouter.sol b/src/TeleportRouter.sol
new file mode 100644
index 0000000..2b49f09
--- /dev/null
+++ b/src/TeleportRouter.sol
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// Copyright (C) 2021 Dai Foundation
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+pragma solidity 0.8.14;
+
+import "./TeleportGUID.sol";
+import "./utils/EnumerableSet.sol";
+
+interface TokenLike {
+ function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
+}
+
+interface GatewayLike {
+ function requestMint(
+ TeleportGUID calldata teleportGUID,
+ uint256 maxFeePercentage,
+ uint256 operatorFee
+ ) external returns (uint256 postFeeAmount, uint256 totalFee);
+ function settle(bytes32 sourceDomain, uint256 batchedDaiToFlush) external;
+}
+
+contract TeleportRouter {
+
+ using EnumerableSet for EnumerableSet.Bytes32Set;
+
+ 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;
+
+ 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);
+
+ modifier auth {
+ require(wards[msg.sender] == 1, "TeleportRouter/not-authorized");
+ _;
+ }
+
+ constructor(address dai_) {
+ dai = TokenLike(dai_);
+ wards[msg.sender] = 1;
+ emit Rely(msg.sender);
+ }
+
+ function rely(address usr) external auth {
+ wards[usr] = 1;
+ emit Rely(usr);
+ }
+
+ function deny(address usr) external auth {
+ wards[usr] = 0;
+ emit Deny(usr);
+ }
+
+ /**
+ * @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
+ * 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`.
+ * @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)
+ */
+ function file(bytes32 what, bytes32 domain, address data) external auth {
+ if (what == "gateway") {
+ address prevGateway = gateways[domain];
+ if(prevGateway == address(0)) {
+ // new domain => add it to allDomains
+ if(data != address(0)) {
+ allDomains.add(domain);
+ }
+ } else {
+ // existing domain
+ domains[prevGateway] = bytes32(0);
+ if(data == address(0)) {
+ // => remove domain from allDomains
+ allDomains.remove(domain);
+ }
+ }
+
+ gateways[domain] = data;
+ if(data != address(0)) {
+ domains[data] = domain;
+ }
+ } else {
+ revert("TeleportRouter/file-unrecognized-param");
+ }
+ emit File(what, domain, data);
+ }
+
+ function numDomains() external view returns (uint256) {
+ return allDomains.length();
+ }
+ function domainAt(uint256 index) external view returns (bytes32) {
+ return allDomains.at(index);
+ }
+ function hasDomain(bytes32 domain) external view returns (bool) {
+ return allDomains.contains(domain);
+ }
+
+ /**
+ * @notice Call a GatewayLike contract to request 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) {
+ require(msg.sender == gateways[teleportGUID.sourceDomain], "TeleportRouter/sender-not-gateway");
+ address gateway = gateways[teleportGUID.targetDomain];
+ require(gateway != address(0), "TeleportRouter/unsupported-target-domain");
+ (postFeeAmount, totalFee) = GatewayLike(gateway).requestMint(teleportGUID, maxFeePercentage, operatorFee);
+ }
+
+ /**
+ * @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 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");
+ address gateway = gateways[targetDomain];
+ 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, batchedDaiToFlush);
+ }
+}
diff --git a/src/utils/EnumerableSet.sol b/src/utils/EnumerableSet.sol
new file mode 100644
index 0000000..68148e9
--- /dev/null
+++ b/src/utils/EnumerableSet.sol
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)
+
+pragma solidity ^0.8.0;
+
+/**
+ * @dev Library for managing
+ * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
+ * types.
+ *
+ * Sets have the following properties:
+ *
+ * - Elements are added, removed, and checked for existence in constant time
+ * (O(1)).
+ * - Elements are enumerated in O(n). No guarantees are made on the ordering.
+ *
+ * ```
+ * contract Example {
+ * // Add the library methods
+ * using EnumerableSet for EnumerableSet.AddressSet;
+ *
+ * // Declare a set state variable
+ * EnumerableSet.AddressSet private mySet;
+ * }
+ * ```
+ *
+ * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
+ * and `uint256` (`UintSet`) are supported.
+ */
+library EnumerableSet {
+ // To implement this library for multiple types with as little code
+ // repetition as possible, we write it in terms of a generic Set type with
+ // bytes32 values.
+ // The Set implementation uses private functions, and user-facing
+ // implementations (such as AddressSet) are just wrappers around the
+ // underlying Set.
+ // This means that we can only create new EnumerableSets for types that fit
+ // in bytes32.
+
+ struct Set {
+ // Storage of set values
+ bytes32[] _values;
+ // Position of the value in the `values` array, plus 1 because index 0
+ // means a value is not in the set.
+ mapping(bytes32 => uint256) _indexes;
+ }
+
+ /**
+ * @dev Add a value to a set. O(1).
+ *
+ * Returns true if the value was added to the set, that is if it was not
+ * already present.
+ */
+ function _add(Set storage set, bytes32 value) private returns (bool) {
+ if (!_contains(set, value)) {
+ set._values.push(value);
+ // The value is stored at length-1, but we add 1 to all indexes
+ // and use 0 as a sentinel value
+ set._indexes[value] = set._values.length;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @dev Removes a value from a set. O(1).
+ *
+ * Returns true if the value was removed from the set, that is if it was
+ * present.
+ */
+ function _remove(Set storage set, bytes32 value) private returns (bool) {
+ // We read and store the value's index to prevent multiple reads from the same storage slot
+ uint256 valueIndex = set._indexes[value];
+
+ if (valueIndex != 0) {
+ // Equivalent to contains(set, value)
+ // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
+ // the array, and then remove the last element (sometimes called as 'swap and pop').
+ // This modifies the order of the array, as noted in {at}.
+
+ uint256 toDeleteIndex = valueIndex - 1;
+ uint256 lastIndex = set._values.length - 1;
+
+ if (lastIndex != toDeleteIndex) {
+ bytes32 lastvalue = set._values[lastIndex];
+
+ // Move the last value to the index where the value to delete is
+ set._values[toDeleteIndex] = lastvalue;
+ // Update the index for the moved value
+ set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
+ }
+
+ // Delete the slot where the moved value was stored
+ set._values.pop();
+
+ // Delete the index for the deleted slot
+ delete set._indexes[value];
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @dev Returns true if the value is in the set. O(1).
+ */
+ function _contains(Set storage set, bytes32 value) private view returns (bool) {
+ return set._indexes[value] != 0;
+ }
+
+ /**
+ * @dev Returns the number of values on the set. O(1).
+ */
+ function _length(Set storage set) private view returns (uint256) {
+ return set._values.length;
+ }
+
+ /**
+ * @dev Returns the value stored at position `index` in the set. O(1).
+ *
+ * Note that there are no guarantees on the ordering of values inside the
+ * array, and it may change when more values are added or removed.
+ *
+ * Requirements:
+ *
+ * - `index` must be strictly less than {length}.
+ */
+ function _at(Set storage set, uint256 index) private view returns (bytes32) {
+ return set._values[index];
+ }
+
+ /**
+ * @dev Return the entire set in an array
+ *
+ * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
+ * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
+ * this function has an unbounded cost, and using it as part of a state-changing function may render the function
+ * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
+ */
+ function _values(Set storage set) private view returns (bytes32[] memory) {
+ return set._values;
+ }
+
+ // Bytes32Set
+
+ struct Bytes32Set {
+ Set _inner;
+ }
+
+ /**
+ * @dev Add a value to a set. O(1).
+ *
+ * Returns true if the value was added to the set, that is if it was not
+ * already present.
+ */
+ function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
+ return _add(set._inner, value);
+ }
+
+ /**
+ * @dev Removes a value from a set. O(1).
+ *
+ * Returns true if the value was removed from the set, that is if it was
+ * present.
+ */
+ function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
+ return _remove(set._inner, value);
+ }
+
+ /**
+ * @dev Returns true if the value is in the set. O(1).
+ */
+ function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
+ return _contains(set._inner, value);
+ }
+
+ /**
+ * @dev Returns the number of values in the set. O(1).
+ */
+ function length(Bytes32Set storage set) internal view returns (uint256) {
+ return _length(set._inner);
+ }
+
+ /**
+ * @dev Returns the value stored at position `index` in the set. O(1).
+ *
+ * Note that there are no guarantees on the ordering of values inside the
+ * array, and it may change when more values are added or removed.
+ *
+ * Requirements:
+ *
+ * - `index` must be strictly less than {length}.
+ */
+ function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
+ return _at(set._inner, index);
+ }
+
+ /**
+ * @dev Return the entire set in an array
+ *
+ * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
+ * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
+ * this function has an unbounded cost, and using it as part of a state-changing function may render the function
+ * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
+ */
+ function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
+ return _values(set._inner);
+ }
+
+ // AddressSet
+
+ struct AddressSet {
+ Set _inner;
+ }
+
+ /**
+ * @dev Add a value to a set. O(1).
+ *
+ * Returns true if the value was added to the set, that is if it was not
+ * already present.
+ */
+ function add(AddressSet storage set, address value) internal returns (bool) {
+ return _add(set._inner, bytes32(uint256(uint160(value))));
+ }
+
+ /**
+ * @dev Removes a value from a set. O(1).
+ *
+ * Returns true if the value was removed from the set, that is if it was
+ * present.
+ */
+ function remove(AddressSet storage set, address value) internal returns (bool) {
+ return _remove(set._inner, bytes32(uint256(uint160(value))));
+ }
+
+ /**
+ * @dev Returns true if the value is in the set. O(1).
+ */
+ function contains(AddressSet storage set, address value) internal view returns (bool) {
+ return _contains(set._inner, bytes32(uint256(uint160(value))));
+ }
+
+ /**
+ * @dev Returns the number of values in the set. O(1).
+ */
+ function length(AddressSet storage set) internal view returns (uint256) {
+ return _length(set._inner);
+ }
+
+ /**
+ * @dev Returns the value stored at position `index` in the set. O(1).
+ *
+ * Note that there are no guarantees on the ordering of values inside the
+ * array, and it may change when more values are added or removed.
+ *
+ * Requirements:
+ *
+ * - `index` must be strictly less than {length}.
+ */
+ function at(AddressSet storage set, uint256 index) internal view returns (address) {
+ return address(uint160(uint256(_at(set._inner, index))));
+ }
+
+ /**
+ * @dev Return the entire set in an array
+ *
+ * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
+ * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
+ * this function has an unbounded cost, and using it as part of a state-changing function may render the function
+ * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
+ */
+ function values(AddressSet storage set) internal view returns (address[] memory) {
+ bytes32[] memory store = _values(set._inner);
+ address[] memory result;
+
+ assembly {
+ result := store
+ }
+
+ return result;
+ }
+
+ // UintSet
+
+ struct UintSet {
+ Set _inner;
+ }
+
+ /**
+ * @dev Add a value to a set. O(1).
+ *
+ * Returns true if the value was added to the set, that is if it was not
+ * already present.
+ */
+ function add(UintSet storage set, uint256 value) internal returns (bool) {
+ return _add(set._inner, bytes32(value));
+ }
+
+ /**
+ * @dev Removes a value from a set. O(1).
+ *
+ * Returns true if the value was removed from the set, that is if it was
+ * present.
+ */
+ function remove(UintSet storage set, uint256 value) internal returns (bool) {
+ return _remove(set._inner, bytes32(value));
+ }
+
+ /**
+ * @dev Returns true if the value is in the set. O(1).
+ */
+ function contains(UintSet storage set, uint256 value) internal view returns (bool) {
+ return _contains(set._inner, bytes32(value));
+ }
+
+ /**
+ * @dev Returns the number of values on the set. O(1).
+ */
+ function length(UintSet storage set) internal view returns (uint256) {
+ return _length(set._inner);
+ }
+
+ /**
+ * @dev Returns the value stored at position `index` in the set. O(1).
+ *
+ * Note that there are no guarantees on the ordering of values inside the
+ * array, and it may change when more values are added or removed.
+ *
+ * Requirements:
+ *
+ * - `index` must be strictly less than {length}.
+ */
+ function at(UintSet storage set, uint256 index) internal view returns (uint256) {
+ return uint256(_at(set._inner, index));
+ }
+
+ /**
+ * @dev Return the entire set in an array
+ *
+ * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
+ * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
+ * this function has an unbounded cost, and using it as part of a state-changing function may render the function
+ * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
+ */
+ function values(UintSet storage set) internal view returns (uint256[] memory) {
+ bytes32[] memory store = _values(set._inner);
+ uint256[] memory result;
+
+ assembly {
+ result := store
+ }
+
+ return result;
+ }
+}