Skip to content

Commit 016bb32

Browse files
committed
Added contracts
1 parent f12f6f8 commit 016bb32

19 files changed

+14008
-1
lines changed

.github/workflows/contracts.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: "contracts"
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
- dev
11+
12+
jobs:
13+
test:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout the repository
17+
uses: actions/checkout@v3
18+
19+
- name: Setup node
20+
uses: actions/setup-node@v3
21+
with:
22+
node-version: "16.18.x"
23+
cache: npm
24+
25+
- name: Install packages
26+
run: npm run contracts-install
27+
shell: bash
28+
29+
- name: Run tests
30+
run: npm run contracts-test

contracts/.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Deployer private key
2+
PRIVATE_KEY=YOUR PRIVATE KEY
3+
4+
# RPC Endpoints
5+
INFURA_KEY=INFURA PROJECT ID
6+
7+
# Additional keys
8+
ETHERSCAN_KEY=ETHERSCAN API KEY

contracts/.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
node_modules
2+
.env
3+
.DS_Store
4+
.npmrc
5+
6+
# Hardhat files
7+
cache
8+
artifacts
9+
coverage.json
10+
coverage
11+
generated-markups
12+
abi
13+
docs/**/*.md
14+
15+
# Typechain generated files
16+
generated-types
17+
18+
# JetBrains IDE files
19+
.idea

contracts/.prettierignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
node_modules
2+
.env
3+
.DS_Store
4+
package-lock.json
5+
6+
# Hardhat files
7+
cache
8+
artifacts
9+
coverage.json
10+
coverage
11+
12+
# Typechain generated files
13+
generated-types

contracts/.prettierrc.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"overrides": [
3+
{
4+
"files": "*.sol",
5+
"options": {
6+
"printWidth": 99,
7+
"tabWidth": 4,
8+
"useTabs": false,
9+
"singleQuote": false,
10+
"bracketSpacing": false
11+
}
12+
},
13+
{
14+
"files": "*.ts",
15+
"options": {
16+
"printWidth": 120,
17+
"tabWidth": 2
18+
}
19+
}
20+
]
21+
}

contracts/.solcover.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
skipFiles: [],
3+
configureYulOptimizer: true,
4+
};

contracts/.solhint.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": "solhint:recommended",
3+
"rules": {
4+
"reentrancy": "warn",
5+
"prettier/prettier": "warn",
6+
"modifier-name-mixedcase": "warn",
7+
"no-empty-blocks": "warn",
8+
"func-visibility": "warn",
9+
"max-states-count": "warn",
10+
"not-rely-on-time": "warn",
11+
"compiler-version": "warn"
12+
}
13+
}

contracts/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Taprootized Atomic Swaps ETH contracts

contracts/contracts/Depositor.sol

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.23;
3+
4+
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
5+
6+
library PoseidonUnit1L {
7+
function poseidon(uint256[1] calldata) public pure returns (uint256) {}
8+
}
9+
10+
/**
11+
* @title Taprootized Atomic Swaps Contract
12+
* @notice This contract facilitates atomic swaps using between Bitcoin and Ethereum, ensuring privacy and security for both parties.
13+
*
14+
* Functionality:
15+
* - Users can deposit ETH into the contract, specifying a recipient, a Poseidon hash of a secret, and a lock time.
16+
* - Deposits can only be withdrawn by the recipient if they provide the correct secret that matches the hash used at the time of deposit.
17+
* - Deposits are locked for a minimum duration, defined by the contract, ensuring that the depositor cannot reclaim them before this period.
18+
* - If the recipient does not withdraw the funds within the lock time, the depositor can reclaim them.
19+
*
20+
* Limitations:
21+
* - The contract only supports ETH deposits and does not handle ERC20 tokens or other assets.
22+
* - The lock time is fixed and cannot be modified after a deposit is made.
23+
* - The contract does not support incremental deposits to the same secret hash; each deposit must have a unique secret hash.
24+
*/
25+
contract Depositor {
26+
/**
27+
* @notice Represents the minimum time (in seconds) that a deposit must be locked in the contract. Set to one hour.
28+
*/
29+
uint256 public constant HOUR = 60 * 60;
30+
31+
/**
32+
* @notice Struct to store details of each deposit.
33+
* @param sender The Ethereum address of the user who created the deposit.
34+
* @param recipient The Ethereum address of the user eligible to withdraw the deposit using the correct secret.
35+
* @param amount The amount of ETH deposited.
36+
* @param lockTime The UNIX timestamp until which the deposit is locked and cannot be withdrawn.
37+
* @param isWithdrawn Boolean flag indicating whether the deposit has been withdrawn. This helps prevent double spending.
38+
*/
39+
struct Deposit {
40+
address sender;
41+
address recipient;
42+
uint256 amount;
43+
uint256 lockTime;
44+
bool isWithdrawn;
45+
}
46+
47+
/**
48+
* @notice Mapping of Poseidon hash of the secret to Deposit structs.
49+
*/
50+
mapping(bytes32 => Deposit) public deposits;
51+
52+
/**
53+
* @notice Emitted when a new deposit is made.
54+
* @param sender The Ethereum address of the user who created the deposit.
55+
* @param recipient The Ethereum address of the user eligible to withdraw the deposit using the correct secret.
56+
* @param amount The amount of ETH deposited.
57+
* @param lockTime The UNIX timestamp until which the deposit is locked and cannot be withdrawn.
58+
* @param secretHash The Poseidon hash of the secret required to withdraw the deposit.
59+
*/
60+
event Deposited(
61+
address indexed sender,
62+
address indexed recipient,
63+
uint256 amount,
64+
uint256 lockTime,
65+
bytes32 secretHash
66+
);
67+
68+
/**
69+
* @notice Emitted when a deposit is successfully withdrawn.
70+
* @param recipient The Ethereum address of the user who withdrew the deposit.
71+
* @param amount The amount of ETH withdrawn.
72+
* @param secret The secret used to withdraw the deposit.
73+
* @param secretHash The Poseidon hash of the secret used to create the deposit.
74+
*/
75+
event Withdrawn(address indexed recipient, uint256 amount, bytes32 secret, bytes32 secretHash);
76+
77+
/**
78+
* @notice Emitted when deposited funds are restored to the sender after the lock time has expired.
79+
* @param sender The Ethereum address of the sender to whom the funds are restored.
80+
* @param amount The amount of ETH restored.
81+
* @param secretHash The Poseidon hash of the secret originally used for the deposit.
82+
*/
83+
event Restored(address indexed sender, uint256 amount, bytes32 secretHash);
84+
85+
/**
86+
* @notice Error thrown when a deposit is attempted with an amount of 0 ETH.
87+
* @param sentAmount The amount of ETH attempted to be deposited, expected to be greater than 0.
88+
*/
89+
error InsufficientDepositAmount(uint256 sentAmount);
90+
91+
/**
92+
* @notice Error thrown when a deposit with the given secret hash already exists.
93+
* @param secretHash The Poseidon hash of the secret for which a deposit already exists.
94+
*/
95+
error DepositAlreadyExists(bytes32 secretHash);
96+
97+
/**
98+
* @notice Error thrown when the provided lock time for a deposit is too short.
99+
* @param providedLockTime The lock time provided for the deposit.
100+
* @param minimumLockTime The minimum required lock time, typically one hour.
101+
*/
102+
error LockTimeTooShort(uint256 providedLockTime, uint256 minimumLockTime);
103+
104+
/**
105+
* @notice Error thrown when an operation (like withdrawal or restoration) is attempted on a non-existent deposit.
106+
* @param secretHash The Poseidon hash of the secret for which the deposit does not exist.
107+
*/
108+
error DepositDoesNotExist(bytes32 secretHash);
109+
110+
/**
111+
* @notice Error thrown when an attempt is made to withdraw or restore an already withdrawn deposit.
112+
* @param secretHash The Poseidon hash of the secret for the deposit that has already been withdrawn.
113+
*/
114+
error DepositAlreadyWithdrawn(bytes32 secretHash);
115+
116+
/**
117+
* @notice Error thrown when an attempt is made to restore funds before the lock time has expired.
118+
* @param currentTime The current UNIX timestamp.
119+
* @param lockTime The UNIX timestamp until which the deposit is locked.
120+
*/
121+
error TimeLockNotExpired(uint256 currentTime, uint256 lockTime);
122+
123+
/**
124+
* @notice Error thrown when an attempt is made to make a deposit with the zero Ethereum address as the recipient.
125+
*/
126+
error ZeroAddressNotAllowed();
127+
128+
/**
129+
* @notice Allows a user to deposit ETH into the contract with a given recipient, secret hash, and lock time.
130+
* @dev Emits a `Deposited` event upon successful deposit. Checks for zero deposit amount, duplicate deposits,
131+
* short lock times, and zero address recipient.
132+
* @param recipient_ The Ethereum address of the recipient eligible to withdraw the deposit using the correct secret.
133+
* @param secretHash_ The Poseidon hash of the secret required for the recipient to withdraw the deposit.
134+
* @param lockTime_ The duration (in seconds) for which the deposit is locked and cannot be withdrawn.
135+
*/
136+
function deposit(address recipient_, bytes32 secretHash_, uint256 lockTime_) external payable {
137+
if (msg.value == 0) revert InsufficientDepositAmount(msg.value);
138+
if (deposits[secretHash_].amount != 0) revert DepositAlreadyExists(secretHash_);
139+
if (lockTime_ < HOUR) revert LockTimeTooShort(lockTime_, HOUR);
140+
if (recipient_ == address(0)) revert ZeroAddressNotAllowed();
141+
142+
deposits[secretHash_] = Deposit({
143+
sender: msg.sender,
144+
recipient: recipient_,
145+
amount: msg.value,
146+
lockTime: block.timestamp + lockTime_,
147+
isWithdrawn: false
148+
});
149+
150+
emit Deposited(msg.sender, recipient_, msg.value, lockTime_, secretHash_);
151+
}
152+
153+
/**
154+
* @notice Allows the recipient to withdraw a deposit using the correct secret.
155+
* @dev Emits a `Withdrawn` event upon successful withdrawal. Checks for non-existent or already withdrawn deposits.
156+
* Uses the PoseidonUnit1L library to hash the provided secret.
157+
* @param secret_ The prototype of the `secretHash` used in the deposit function.
158+
*/
159+
function withdraw(bytes32 secret_) external {
160+
bytes32 secretHash_ = bytes32(PoseidonUnit1L.poseidon([uint256(secret_)]));
161+
162+
Deposit storage userDeposit = deposits[secretHash_];
163+
164+
uint256 depositAmount_ = userDeposit.amount;
165+
address depositRecipient_ = userDeposit.recipient;
166+
167+
if (depositAmount_ == 0) revert DepositDoesNotExist(secretHash_);
168+
if (userDeposit.isWithdrawn) revert DepositAlreadyWithdrawn(secretHash_);
169+
170+
userDeposit.isWithdrawn = true;
171+
172+
(bool success_, bytes memory data_) = payable(depositRecipient_).call{
173+
value: depositAmount_
174+
}("");
175+
Address.verifyCallResult(success_, data_);
176+
177+
emit Withdrawn(depositRecipient_, depositAmount_, secret_, secretHash_);
178+
}
179+
180+
/**
181+
* @notice Allows the sender to restore a deposit back to themselves after the lock time has expired.
182+
* @dev Emits a `Restored` event upon successful restoration. Checks for non-existent, already withdrawn,
183+
* or not yet expired deposits.
184+
* @param secretHash_ The Poseidon hash of the secret used when the deposit was created.
185+
*/
186+
function restore(bytes32 secretHash_) external {
187+
Deposit storage userDeposit = deposits[secretHash_];
188+
189+
uint256 depositAmount_ = userDeposit.amount;
190+
191+
if (depositAmount_ == 0) revert DepositDoesNotExist(secretHash_);
192+
if (userDeposit.isWithdrawn) revert DepositAlreadyWithdrawn(secretHash_);
193+
if (userDeposit.lockTime > block.timestamp)
194+
revert TimeLockNotExpired(block.timestamp, userDeposit.lockTime);
195+
196+
userDeposit.isWithdrawn = true;
197+
198+
(bool success_, bytes memory data_) = payable(userDeposit.sender).call{
199+
value: depositAmount_
200+
}("");
201+
Address.verifyCallResult(success_, data_);
202+
203+
emit Restored(userDeposit.sender, depositAmount_, secretHash_);
204+
}
205+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Deployer, Reporter } from "@solarity/hardhat-migrate";
2+
3+
import { Depositor__factory } from "@ethers-v6";
4+
5+
import { poseidonContract } from "circomlibjs";
6+
7+
export = async (deployer: Deployer) => {
8+
await deployer.deploy({
9+
contractName: "contracts/Depositor.sol:PoseidonUnit1L",
10+
bytecode: poseidonContract.createCode(1),
11+
abi: poseidonContract.generateABI(1),
12+
});
13+
14+
const depositor = await deployer.deploy(Depositor__factory);
15+
16+
Reporter.reportContracts(["Depositor", await depositor.getAddress()]);
17+
};

0 commit comments

Comments
 (0)