SwapCreator.sol 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. // SPDX-License-Identifier: LGPLv3
  2. pragma solidity ^0.8.20;
  3. import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
  4. import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
  5. import {Secp256k1} from "./Secp256k1.sol";
  6. // SwapCreator facilitates swapping between Alice, a party that has an EVM
  7. // native currency or a token (ERC-20 or compatible API) that she wants to
  8. // exchange cross-chain for a different currency, and Bob, a party that has the
  9. // other chain's currency and wishes to exchange it for Alice's currency.
  10. contract SwapCreator is Secp256k1 {
  11. using SafeERC20 for IERC20;
  12. // Stage represents the swap state. It is PENDING when `newSwap` is called
  13. // to create and fund the swap. Alice sets Stage to READY, via `setReady`,
  14. // after verifying that funds are locked on the other chain. Bob cannot
  15. // claim the swap funds until Alice sets the swap Stage to READY. The Stage
  16. // is set to COMPLETED when Bob claims directly via `claim` or indirectly
  17. // via `claimRelayer`, or by Alice calling `refund`.
  18. enum Stage {
  19. INVALID,
  20. PENDING,
  21. READY,
  22. COMPLETED
  23. }
  24. // swaps maps from a swap ID to the swap's current Stage
  25. mapping(bytes32 => Stage) public swaps;
  26. // Swap stores the swap parameters, the hash of which forms the swap ID.
  27. struct Swap {
  28. // owner is the address of Alice, who initiates the swap by calling
  29. // `newSwap`. Only the owner is allowed to call `setReady` or `refund`.
  30. address payable owner;
  31. // claimer is the address of Bob. Only the claimer can call `claim` or
  32. // sign a RelaySwap object that `claimRelayer` will accept the signature
  33. // for.
  34. address payable claimer;
  35. // claimCommitment is the Keccak-256 hash of the expected secp256k1
  36. // public key derived from the secret (private key) that Bob sends when
  37. // claiming. Alice receives this commitment off-chain.
  38. bytes32 claimCommitment;
  39. // refundCommitment is the Keccak-256 hash of the expected secp256k1
  40. // public key derived from the secret (private key) that Alice sends if
  41. // refunding.
  42. bytes32 refundCommitment;
  43. // timeout1 is the block timestamp before which Alice can call
  44. // either `setReady` or `refund`.
  45. uint256 timeout1;
  46. // timeout2 is the block timestamp after which Bob cannot claim, only
  47. // Alice can refund.
  48. uint256 timeout2;
  49. // asset is address(0) for EVM native currency swaps, or it is the
  50. // address of the token that Alice is providing.
  51. address asset;
  52. // value is the wei or token unit amount that Alice locked in the contract
  53. uint256 value;
  54. // nonce is a random value chosen by Alice
  55. uint256 nonce;
  56. }
  57. // RelaySwap contains additional information required for relayed claim
  58. // transactions. This entire structure is encoded and signed by the swap
  59. // claimer, and the signature is passed to `claimRelayer`.
  60. struct RelaySwap {
  61. // swap specifies which swap is being claimed
  62. Swap swap;
  63. // fee is the wei amount paid to the relayer
  64. uint256 fee;
  65. // relayerHash Keccak-256 hash of (relayer's payout address || 4-byte salt)
  66. bytes32 relayerHash;
  67. // swapCreator is the address of the swap's contract
  68. address swapCreator;
  69. }
  70. event New(
  71. bytes32 swapID,
  72. bytes32 claimKey,
  73. bytes32 refundKey,
  74. address claimer,
  75. uint256 timeout1,
  76. uint256 timeout2,
  77. address asset,
  78. uint256 value,
  79. uint256 nonce
  80. );
  81. event Ready(bytes32 indexed swapID);
  82. event Claimed(bytes32 indexed swapID, bytes32 indexed s);
  83. event Refunded(bytes32 indexed swapID, bytes32 indexed s);
  84. // thrown when the value parameter to `newSwap` is zero
  85. error ZeroValue();
  86. // thrown when either of the claimCommitment or refundCommitment parameters
  87. // passed to `newSwap` are zero
  88. error InvalidSwapKey();
  89. // thrown when the claimer parameter for `newSwap` is the zero address
  90. error InvalidClaimer();
  91. // thrown when the timeout1 or timeout2 parameters for `newSwap` are zero
  92. error InvalidTimeout();
  93. // thrown when msg.value of a `newSwap` transaction has the wrong value
  94. error InvalidValue();
  95. // thrown when trying to initiate a swap with an ID that already exists
  96. error SwapAlreadyExists();
  97. // thrown when trying to call `setReady` on a swap that is not in the
  98. // PENDING stage
  99. error SwapNotPending();
  100. // thrown when the caller of `setReady` or `refund` is not the swap owner
  101. error OnlySwapOwner();
  102. // thrown when the signer of the relayed transaction is not the swap's
  103. // claimer
  104. error OnlySwapClaimer();
  105. // thrown when trying to call `claim` or `refund` on an invalid swap
  106. error InvalidSwap();
  107. // thrown when trying to call `claim` or `refund` on a swap that's already
  108. // completed
  109. error SwapCompleted();
  110. // thrown when trying to call `claim` on a swap that's not set to ready or
  111. // the first timeout has not been reached
  112. error TooEarlyToClaim();
  113. // thrown when trying to call `claim` on a swap where the second timeout has
  114. // been reached
  115. error TooLateToClaim();
  116. // thrown when it's the counterparty's turn to claim and refunding is not
  117. // allowed
  118. error NotTimeToRefund();
  119. // thrown when the provided secret does not match its expected public key
  120. // hash
  121. error InvalidSecret();
  122. // thrown when the signature of a `RelaySwap` is invalid
  123. error InvalidSignature();
  124. // thrown when the SwapCreator address is a `RelaySwap` is not the address
  125. // of this contract
  126. error InvalidContractAddress();
  127. // thrown when the hash of the relayer address and salt passed to
  128. // `claimRelayer` does not match the relayer hash in `RelaySwap`
  129. error InvalidRelayerAddress();
  130. // `newSwap` creates a new Swap instance using the passed parameters and
  131. // locks Alice's native EVM currency or token asset in the contract. On
  132. // success, the swap ID is returned.
  133. //
  134. // Note that the duration values are distinct from the timeout values:
  135. //
  136. // _timeoutDuration1:
  137. // duration, in seconds, between the current block timestamp and
  138. // timeout1
  139. //
  140. // _timeoutDuration2:
  141. // duration, in seconds, between timeout1 and timeout2
  142. //
  143. function newSwap(
  144. bytes32 _claimCommitment,
  145. bytes32 _refundCommitment,
  146. address payable _claimer,
  147. uint256 _timeoutDuration1,
  148. uint256 _timeoutDuration2,
  149. address _asset,
  150. uint256 _value,
  151. uint256 _nonce
  152. ) public payable returns (bytes32) {
  153. if (_value == 0) revert ZeroValue();
  154. if (_asset == address(0)) {
  155. if (_value != msg.value) revert InvalidValue();
  156. } else {
  157. // transfer the token amount to this contract
  158. // WARN: fee-on-transfer tokens are not supported
  159. IERC20(_asset).safeTransferFrom(msg.sender, address(this), _value);
  160. }
  161. if (_claimCommitment == 0 || _refundCommitment == 0) revert InvalidSwapKey();
  162. if (_claimer == address(0)) revert InvalidClaimer();
  163. if (_timeoutDuration1 == 0 || _timeoutDuration2 == 0) revert InvalidTimeout();
  164. Swap memory swap = Swap({
  165. owner: payable(msg.sender),
  166. claimCommitment: _claimCommitment,
  167. refundCommitment: _refundCommitment,
  168. claimer: _claimer,
  169. timeout1: block.timestamp + _timeoutDuration1,
  170. timeout2: block.timestamp + _timeoutDuration1 + _timeoutDuration2,
  171. asset: _asset,
  172. value: _value,
  173. nonce: _nonce
  174. });
  175. bytes32 swapID = keccak256(abi.encode(swap));
  176. // ensure that we are not overriding an existing swap
  177. if (swaps[swapID] != Stage.INVALID) revert SwapAlreadyExists();
  178. emit New(
  179. swapID,
  180. _claimCommitment,
  181. _refundCommitment,
  182. _claimer,
  183. swap.timeout1,
  184. swap.timeout2,
  185. swap.asset,
  186. swap.value,
  187. swap.nonce
  188. );
  189. swaps[swapID] = Stage.PENDING;
  190. return swapID;
  191. }
  192. // Alice should call `setReady` before timeout1 and after verifying that Bob
  193. // locked his swap funds.
  194. function setReady(Swap memory _swap) public {
  195. bytes32 swapID = keccak256(abi.encode(_swap));
  196. if (swaps[swapID] != Stage.PENDING) revert SwapNotPending();
  197. if (_swap.owner != msg.sender) revert OnlySwapOwner();
  198. swaps[swapID] = Stage.READY;
  199. emit Ready(swapID);
  200. }
  201. // Bob can call `claim` if either of these hold true:
  202. // (1) Alice has set the swap to `ready` and it's before timeout1
  203. // (2) It is between timeout1 and timeout2
  204. function claim(Swap memory _swap, bytes32 _secret) public {
  205. if (msg.sender != _swap.claimer) revert OnlySwapClaimer();
  206. _claim(_swap, _secret);
  207. if (_swap.asset == address(0)) {
  208. // Transfer the swap value as the EVM's native currency
  209. _swap.claimer.transfer(_swap.value);
  210. } else {
  211. // Transfer the swap value as a token amount.
  212. // WARNING: this will FAIL for fee-on-transfer or rebasing tokens if
  213. // the token transfer reverts (i.e. if this contract does not
  214. // contain _swap.value tokens), exposing Bob's secret while giving
  215. // him nothing.
  216. IERC20(_swap.asset).safeTransfer(_swap.claimer, _swap.value);
  217. }
  218. }
  219. // Anyone can call `claimRelayer` if they receive a signed _relaySwap object
  220. // from Bob. The same rules for when Bob can call `claim` apply here when a
  221. // 3rd party relays a claim for Bob. This version of claiming transfers a
  222. // _relaySwap.fee to _relayer. To prevent front-running, while not requiring
  223. // Bob to know the relayer's payout address, Bob only signs a salted hash of
  224. // the relayer's payout address in _relaySwap.relayerHash.
  225. // Note: claimRelayer will revert if the swap value is less than the relayer
  226. // fee; in that case, Bob must call claim directly.
  227. function claimRelayer(
  228. RelaySwap memory _relaySwap,
  229. bytes32 _secret,
  230. address payable _relayer,
  231. uint32 _salt,
  232. uint8 v,
  233. bytes32 r,
  234. bytes32 s
  235. ) public {
  236. address signer = ecrecover(keccak256(abi.encode(_relaySwap)), v, r, s);
  237. if (signer != _relaySwap.swap.claimer) revert InvalidSignature();
  238. if (address(this) != _relaySwap.swapCreator) revert InvalidContractAddress();
  239. if (keccak256(abi.encodePacked(_relayer, _salt)) != _relaySwap.relayerHash)
  240. revert InvalidRelayerAddress();
  241. _claim(_relaySwap.swap, _secret);
  242. // send ether to swap claimer, subtracting the relayer fee
  243. if (_relaySwap.swap.asset == address(0)) {
  244. _relaySwap.swap.claimer.transfer(_relaySwap.swap.value - _relaySwap.fee);
  245. payable(_relayer).transfer(_relaySwap.fee);
  246. } else {
  247. // WARN: this will FAIL for fee-on-transfer or rebasing tokens if the token
  248. // transfer reverts (i.e. if this contract does not contain _swap.value tokens),
  249. // exposing Bob's secret while giving him nothing.
  250. IERC20(_relaySwap.swap.asset).safeTransfer(
  251. _relaySwap.swap.claimer,
  252. _relaySwap.swap.value - _relaySwap.fee
  253. );
  254. IERC20(_relaySwap.swap.asset).safeTransfer(_relayer, _relaySwap.fee);
  255. }
  256. }
  257. function _claim(Swap memory _swap, bytes32 _secret) internal {
  258. bytes32 swapID = keccak256(abi.encode(_swap));
  259. Stage swapStage = swaps[swapID];
  260. if (swapStage == Stage.INVALID) revert InvalidSwap();
  261. if (swapStage == Stage.COMPLETED) revert SwapCompleted();
  262. if (block.timestamp < _swap.timeout1 && swapStage != Stage.READY) revert TooEarlyToClaim();
  263. if (block.timestamp >= _swap.timeout2) revert TooLateToClaim();
  264. verifySecret(_secret, _swap.claimCommitment);
  265. emit Claimed(swapID, _secret);
  266. swaps[swapID] = Stage.COMPLETED;
  267. }
  268. // Alice can `refund` her swap funds:
  269. // - Until timeout1, unless she called `setReady`
  270. // - After timeout2, independent of whether she called `setReady`
  271. function refund(Swap memory _swap, bytes32 _secret) public {
  272. bytes32 swapID = keccak256(abi.encode(_swap));
  273. Stage swapStage = swaps[swapID];
  274. if (swapStage == Stage.INVALID) revert InvalidSwap();
  275. if (swapStage == Stage.COMPLETED) revert SwapCompleted();
  276. if (_swap.owner != msg.sender) revert OnlySwapOwner();
  277. if (
  278. block.timestamp < _swap.timeout2 &&
  279. (block.timestamp > _swap.timeout1 || swapStage == Stage.READY)
  280. ) revert NotTimeToRefund();
  281. verifySecret(_secret, _swap.refundCommitment);
  282. emit Refunded(swapID, _secret);
  283. // send asset back to swap owner
  284. swaps[swapID] = Stage.COMPLETED;
  285. if (_swap.asset == address(0)) {
  286. _swap.owner.transfer(_swap.value);
  287. } else {
  288. IERC20(_swap.asset).safeTransfer(_swap.owner, _swap.value);
  289. }
  290. }
  291. function verifySecret(bytes32 _secret, bytes32 _hashedPubkey) internal pure {
  292. if (!mulVerify(uint256(_secret), uint256(_hashedPubkey))) revert InvalidSecret();
  293. }
  294. }