Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3b35eae

Browse files
authoredNov 6, 2024··
Add createAndSignBitcoinTx precompile (#3)
* forge install: openzeppelin-contracts v5.0.2 * add UTXO manager contract * rm utxo manager * rm oz dep * fmt
1 parent dd045d6 commit 3b35eae

File tree

6 files changed

+55
-47
lines changed

6 files changed

+55
-47
lines changed
 

‎foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ remappings_generated = true
1616
remappings_regenerate = true
1717

1818
# whether to suffix the remapping with the version: `name-a.b.c`
19-
remappings_version = false
19+
remappings_version = false

‎remappings.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
@solady=dependencies/solady-0.0.234/src
1+
@solady=dependencies/solady-0.0.234/src

‎soldeer.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
21
[[dependencies]]
32
name = "solady"
43
version = "0.0.234"
54
source = "https://soldeer-revisions.s3.amazonaws.com/solady/0_0_234_19-08-2024_05:13:28_solady.zip"
65
checksum = "a33f6d46a65c6a1adebed83365c128eab4c3e9d124b68fe5dea1a675a7f661a8"
6+
integrity = "afedc5ab760d5e83fcb7d4181fdb77fa2d829e6aca88653536c258fbd8656cdc"

‎src/GetBlock.sol

Lines changed: 0 additions & 14 deletions
This file was deleted.

‎src/lib/CorsaBitcoin.sol

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ pragma solidity 0.8.26;
44
library CorsaBitcoin {
55
address private constant BTC_PRECOMPILE = address(0x999);
66

7-
bytes4 private constant BROADCAST_LEADING_BYTE = 0x00000000;
8-
bytes4 private constant GET_BLOCK_HEIGHT_LEADING_BYTE = 0x00000001;
9-
bytes4 private constant DECODE_LEADING_BYTE = 0x00000002;
10-
bytes4 private constant CHECKSIG_LEADING_BYTE = 0x00000003;
11-
bytes4 private constant ADDRESS_CONVERT_LEADING_BYTE = 0x00000004;
12-
bytes4 private constant SEND_BTC_LEADING_BYTE = 0x00000005;
7+
bytes4 private constant BROADCAST_LEADING_BYTES = 0x00000001;
8+
bytes4 private constant DECODE_LEADING_BYTES = 0x00000002;
9+
bytes4 private constant CHECKSIG_LEADING_BYTES = 0x00000003;
10+
bytes4 private constant ADDRESS_CONVERT_LEADING_BYTES = 0x00000004;
11+
bytes4 private constant CREATE_AND_SIGN_LEADING_BYTES = 0x00000005;
1312

1413
struct Output {
1514
string addr;
@@ -33,39 +32,50 @@ library CorsaBitcoin {
3332

3433
error PrecompileCallFailed();
3534

36-
function decodeBitcoinTx(bytes calldata signedTx) internal view returns (BitcoinTx memory) {
35+
function decodeBitcoinTx(bytes memory signedTx) internal view returns (BitcoinTx memory) {
3736
(bool success, bytes memory returndata) =
38-
BTC_PRECOMPILE.staticcall(abi.encodePacked(DECODE_LEADING_BYTE, signedTx));
37+
BTC_PRECOMPILE.staticcall(abi.encodePacked(DECODE_LEADING_BYTES, signedTx));
3938
if (!success) revert PrecompileCallFailed();
4039
return abi.decode(returndata, (BitcoinTx));
4140
}
4241

4342
function checkSignature(bytes calldata signedTx) internal view returns (bool) {
44-
(bool success,) = BTC_PRECOMPILE.staticcall(abi.encodePacked(CHECKSIG_LEADING_BYTE, signedTx));
43+
(bool success,) = BTC_PRECOMPILE.staticcall(abi.encodePacked(CHECKSIG_LEADING_BYTES, signedTx));
4544
return success;
4645
}
4746

4847
function convertEthToBtcAddress(address ethAddress) internal returns (bytes memory) {
4948
(bool success, bytes memory returndata) =
50-
BTC_PRECOMPILE.call(abi.encodePacked(ADDRESS_CONVERT_LEADING_BYTE, ethAddress));
49+
BTC_PRECOMPILE.call(abi.encodePacked(ADDRESS_CONVERT_LEADING_BYTES, ethAddress));
5150
if (!success) revert PrecompileCallFailed();
5251
return returndata;
5352
}
5453

55-
function broadcastBitcoinTx(bytes calldata signedTx) internal returns (bool) {
56-
(bool success,) = BTC_PRECOMPILE.call(abi.encodePacked(BROADCAST_LEADING_BYTE, signedTx));
57-
return success;
58-
}
54+
function broadcastBitcoinTx(bytes memory signedTx) internal returns (bytes32) {
55+
(bool success, bytes memory returndata) =
56+
BTC_PRECOMPILE.call(abi.encodePacked(BROADCAST_LEADING_BYTES, signedTx));
57+
if (!success) revert PrecompileCallFailed();
5958

60-
function sendBitcoin(address from, uint256 amount, string calldata destination) internal returns (bool) {
61-
(bool success,) = BTC_PRECOMPILE.call(abi.encodePacked(SEND_BTC_LEADING_BYTE, from, amount, destination));
62-
return success;
59+
require(returndata.length == 32, "Invalid txid length");
60+
61+
bytes32 txid;
62+
assembly {
63+
txid := mload(add(returndata, 32))
64+
}
65+
66+
return txid;
6367
}
6468

65-
function getCurrentBlockHeight() internal view returns (uint256) {
66-
(bool success, bytes memory returndata) =
67-
BTC_PRECOMPILE.staticcall(abi.encodePacked(GET_BLOCK_HEIGHT_LEADING_BYTE));
69+
function createAndSignBitcoinTx(address signer, uint64 amount, uint64 blockHeight, string memory destinationAddress)
70+
internal
71+
returns (bytes memory)
72+
{
73+
bytes memory inputData =
74+
abi.encode(CREATE_AND_SIGN_LEADING_BYTES, signer, amount, blockHeight, destinationAddress);
75+
76+
(bool success, bytes memory returndata) = BTC_PRECOMPILE.call(inputData);
6877
if (!success) revert PrecompileCallFailed();
69-
return abi.decode(returndata, (uint256));
78+
79+
return returndata;
7080
}
7181
}

‎src/uBTC.sol

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ contract uBTC is WETH, Ownable {
1313
error UnsignedInput();
1414
error InvalidLocktime();
1515
error BroadcastFailure();
16+
error AmountTooBig();
17+
18+
event Deposit(bytes32 txid, uint256 amount);
19+
event Withdraw(bytes32 txid, uint256 amount);
1620

1721
constructor() WETH() Ownable() {
1822
_initializeOwner(msg.sender);
@@ -43,6 +47,10 @@ contract uBTC is WETH, Ownable {
4347
CorsaBitcoin.BitcoinTx memory btcTx = CorsaBitcoin.decodeBitcoinTx(signedTx);
4448

4549
// input validations
50+
if (amount >= type(uint64).max) {
51+
revert AmountTooBig();
52+
}
53+
4654
if (btcTx.outputs.length < 1 || btcTx.outputs[0].value < amount) {
4755
revert InsufficientDeposit();
4856
}
@@ -72,19 +80,23 @@ contract uBTC is WETH, Ownable {
7280
_mint(msg.sender, amount);
7381

7482
// Broadcast signed btc tx
75-
if (!CorsaBitcoin.broadcastBitcoinTx(signedTx)) {
76-
revert BroadcastFailure();
77-
}
83+
bytes32 txid = CorsaBitcoin.broadcastBitcoinTx(signedTx);
84+
85+
emit Deposit(txid, amount);
7886
}
7987

80-
function withdraw(uint256 amount, string calldata dest) public {
88+
function withdraw(uint64 amount, uint32 btcBlockHeight, string calldata dest) public {
8189
// burn uBTC
82-
_burn(msg.sender, amount);
90+
// TODO(powvt): how to account for gas fees? burn and have user pay?
91+
_burn(msg.sender, uint256(amount));
8392

84-
// sign BTC tx for sending amount to specified destination, then broadcast tx
85-
if (!CorsaBitcoin.sendBitcoin(address(this), amount, dest)) {
86-
revert BroadcastFailure();
87-
}
93+
// Create Bitcoin transaction using the UTXOs
94+
bytes memory signedTx = CorsaBitcoin.createAndSignBitcoinTx(address(this), amount, btcBlockHeight, dest);
95+
96+
// Broadcast signed BTC tx
97+
bytes32 txid = CorsaBitcoin.broadcastBitcoinTx(signedTx);
98+
99+
emit Withdraw(txid, amount);
88100
}
89101

90102
function adminBurn(address wallet, uint256 amount) public onlyOwner {

0 commit comments

Comments
 (0)
Please sign in to comment.