Skip to content

Commit d189f3d

Browse files
ratikalbertandrejev
authored andcommitted
wip
1 parent 91f9601 commit d189f3d

File tree

5 files changed

+264
-43
lines changed

5 files changed

+264
-43
lines changed

script/DeployMaxBTCERC20.script.sol

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.28;
33

4-
import { Script } from "forge-std/Script.sol";
5-
import { console } from "forge-std/console.sol";
6-
import { MaxBTCERC20 } from "../src/MaxBTCERC20.sol";
7-
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
4+
import {Script} from "forge-std/Script.sol";
5+
import {console} from "forge-std/console.sol";
6+
import {MaxBTCERC20} from "../src/MaxBTCERC20.sol";
7+
import {
8+
ERC1967Proxy
9+
} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
810

911
contract DeployMaxBTCERC20 is Script {
1012
function run() external {
1113
address implementation = vm.envAddress("IMPLEMENTATION");
1214
address owner = vm.envAddress("OWNER");
1315
address ics20 = vm.envAddress("ICS20");
16+
address core = vm.envAddress("CORE");
1417
string memory name = vm.envString("TOKEN_NAME");
1518
string memory symbol = vm.envString("TOKEN_SYMBOL");
1619

17-
bytes memory initializeCall = abi.encodeCall(MaxBTCERC20.initialize, (owner, ics20, name, symbol));
20+
bytes memory initializeCall = abi.encodeCall(
21+
MaxBTCERC20.initialize,
22+
(owner, ics20, core, name, symbol)
23+
);
1824

1925
vm.startBroadcast();
2026
ERC1967Proxy proxy = new ERC1967Proxy(implementation, initializeCall);
@@ -25,6 +31,7 @@ contract DeployMaxBTCERC20 is Script {
2531
console.log(" Implementation: ", implementation);
2632
console.log(" Owner: ", owner);
2733
console.log(" ICS20: ", ics20);
34+
console.log(" CORE: ", core);
2835
console.log(" Name: ", name);
2936
console.log(" Symbol: ", symbol);
3037
}

src/MaxBTCCore.sol

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.8.28;
3+
4+
import {
5+
Initializable
6+
} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
7+
import {
8+
UUPSUpgradeable
9+
} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
10+
import {
11+
OwnableUpgradeable
12+
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
13+
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
14+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
15+
import {
16+
SafeERC20
17+
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
18+
import {Receiver} from "./Receiver.sol";
19+
import {MaxBTCERC20} from "./MaxBTCERC20.sol";
20+
import {WithdrawalToken} from "./WithdrawalToken.sol";
21+
22+
contract MaxBTCCore is Initializable, UUPSUpgradeable, OwnableUpgradeable {
23+
struct CoreConfig {
24+
address depositToken;
25+
address maxBTCToken;
26+
address withdrawalToken;
27+
address exchangeRateReceiver;
28+
uint256 exchangeRateStalePeriod;
29+
}
30+
31+
/// @notice Events
32+
33+
event Deposit(
34+
address indexed depositor,
35+
uint256 depositAmount,
36+
uint256 maxBTCMinted
37+
);
38+
39+
event Withdrawal(
40+
address indexed withdrawer,
41+
uint256 maxBTCBurned,
42+
uint256 batchId
43+
);
44+
45+
/// @notice Errors
46+
47+
error InvalidDepositTokenAddress();
48+
error InvalidMaxBTCTokenAddress();
49+
error InvalidWithdrawalTokenAddress();
50+
error InvalidExchangeRateReceiverAddress();
51+
error ExchangeRateStale();
52+
53+
/// @dev keccak256(abi.encode(uint256(keccak256("maxbtc.core.config")) - 1)) & ~bytes32(uint256(0xff))
54+
bytes32 private constant CONFIG_STORAGE_SLOT =
55+
0xe8041c5a119ce847809f9491390b5e4b81852379983e998195264ecb0ca5b100;
56+
57+
function _getCoreConfig() private pure returns (CoreConfig storage $) {
58+
assembly {
59+
$.slot := CONFIG_STORAGE_SLOT
60+
}
61+
}
62+
63+
function initialize(
64+
address owner,
65+
address _depositToken,
66+
address _withdrawalToken,
67+
address _exchangeRateReceiver,
68+
uint256 _exchangeRateStalePeriod
69+
) public initializer {
70+
__Ownable_init(owner);
71+
transferOwnership(owner);
72+
if (_depositToken == address(0)) {
73+
revert InvalidDepositTokenAddress();
74+
}
75+
if (_withdrawalToken == address(0)) {
76+
revert InvalidWithdrawalTokenAddress();
77+
}
78+
if (_exchangeRateReceiver == address(0)) {
79+
revert InvalidExchangeRateReceiverAddress();
80+
}
81+
CoreConfig storage config = _getCoreConfig();
82+
config.depositToken = _depositToken;
83+
config.withdrawalToken = _withdrawalToken;
84+
config.exchangeRateReceiver = _exchangeRateReceiver;
85+
config.exchangeRateStalePeriod = _exchangeRateStalePeriod;
86+
}
87+
88+
function deposit(uint256 amount) external {
89+
CoreConfig storage config = _getCoreConfig();
90+
SafeERC20.safeTransferFrom(
91+
IERC20(config.depositToken),
92+
msg.sender,
93+
address(this),
94+
amount
95+
);
96+
(uint256 exchangeRate, uint256 lastUpdated) = Receiver(
97+
config.exchangeRateReceiver
98+
).getLatest();
99+
require(
100+
block.timestamp - lastUpdated < config.exchangeRateStalePeriod,
101+
ExchangeRateStale()
102+
);
103+
uint256 maxBTCToMint = (amount * 1e18) / exchangeRate;
104+
MaxBTCERC20(config.maxBTCToken).mint(msg.sender, maxBTCToMint);
105+
emit Deposit(msg.sender, amount, maxBTCToMint);
106+
}
107+
108+
function withdraw(uint256 maxBTCAmount) external {
109+
CoreConfig storage config = _getCoreConfig();
110+
MaxBTCERC20(config.maxBTCToken).burn(_msgSender(), maxBTCAmount);
111+
uint256 batchId = 0;
112+
WithdrawalToken(config.withdrawalToken).mint(
113+
_msgSender(),
114+
batchId,
115+
maxBTCAmount,
116+
""
117+
);
118+
emit Withdrawal(_msgSender(), maxBTCAmount, batchId);
119+
}
120+
121+
function _authorizeUpgrade(
122+
address newImplementation
123+
) internal override onlyOwner {}
124+
}

src/MaxBTCERC20.sol

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,42 @@
99
// - disable renounceOwnership()
1010
pragma solidity ^0.8.28;
1111

12-
import { IMintableAndBurnable } from "./IMintableAndBurnable.sol";
13-
import { ERC20Upgradeable } from "@openzeppelin-upgradeable/token/ERC20/ERC20Upgradeable.sol";
14-
import { UUPSUpgradeable } from "@openzeppelin-contracts/proxy/utils/UUPSUpgradeable.sol";
15-
import { Ownable2StepUpgradeable } from "@openzeppelin-upgradeable/access/Ownable2StepUpgradeable.sol";
16-
import { StorageSlot } from "@openzeppelin/contracts/utils/StorageSlot.sol";
12+
import {IMintableAndBurnable} from "./IMintableAndBurnable.sol";
13+
import {
14+
ERC20Upgradeable
15+
} from "@openzeppelin-upgradeable/token/ERC20/ERC20Upgradeable.sol";
16+
import {
17+
UUPSUpgradeable
18+
} from "@openzeppelin-contracts/proxy/utils/UUPSUpgradeable.sol";
19+
import {
20+
OwnableUpgradeable
21+
} from "@openzeppelin-upgradeable/access/OwnableUpgradeable.sol";
22+
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
1723

18-
contract MaxBTCERC20 is IMintableAndBurnable, UUPSUpgradeable, ERC20Upgradeable, Ownable2StepUpgradeable {
19-
/// @notice Caller is not the ICS20 contract
24+
contract MaxBTCERC20 is
25+
IMintableAndBurnable,
26+
UUPSUpgradeable,
27+
ERC20Upgradeable,
28+
OwnableUpgradeable
29+
{
30+
/// @notice Caller is not allowed
2031
/// @param caller The address of the caller
21-
error CallerIsNotICS20(address caller);
32+
error CallerIsNotAllowed(address caller);
2233

23-
/// @notice Provided ICS20 address is invalid
24-
error InvalidICS20();
34+
struct TokenConfig {
35+
address ics20;
36+
address core;
37+
}
2538

26-
/// @notice ERC-7201 slot for the ICS20 contract address
27-
/// @dev keccak256(abi.encode(uint256(keccak256("maxbtc.erc20.ics20")) - 1)) & ~bytes32(uint256(0xff))
28-
bytes32 private constant ICS20_STORAGE_SLOT = 0xaa9b9403d129a09996409713bb21f8632c135ae1789678b7128d16411b23e500;
39+
/// @dev keccak256(abi.encode(uint256(keccak256("maxbtc.erc20.config")) - 1)) & ~bytes32(uint256(0xff))
40+
bytes32 private constant CONFIG_STORAGE_SLOT =
41+
0x60e64ce940b41f99536b34ed9aceefb0cc4425527635a944fc8718b4d4247c00;
42+
43+
function _getCoreConfig() private pure returns (TokenConfig storage $) {
44+
assembly {
45+
$.slot := CONFIG_STORAGE_SLOT
46+
}
47+
}
2948

3049
/// @dev This contract is meant to be deployed by a proxy, so the constructor is not used
3150
// natlint-disable-next-line MissingNotice
@@ -36,31 +55,35 @@ contract MaxBTCERC20 is IMintableAndBurnable, UUPSUpgradeable, ERC20Upgradeable,
3655
/// @notice Initializes the MaxBTCERC20 contract
3756
/// @param owner_ The owner of the contract, allowing it to be upgraded
3857
/// @param ics20_ The ICS20 contract address
58+
/// @param core_ The Core contract address
3959
/// @param name_ The name of the token
4060
/// @param symbol_ The symbol of the token
4161
function initialize(
4262
address owner_,
4363
address ics20_,
64+
address core_,
4465
string calldata name_,
4566
string calldata symbol_
46-
)
47-
external
48-
initializer
49-
{
67+
) external initializer {
5068
__ERC20_init(name_, symbol_);
5169
__Ownable_init(owner_);
52-
53-
if (ics20_ == address(0)) {
54-
revert InvalidICS20();
55-
}
56-
57-
StorageSlot.getAddressSlot(ICS20_STORAGE_SLOT).value = ics20_;
70+
TokenConfig storage config = _getCoreConfig();
71+
config.ics20 = ics20_;
72+
config.core = core_;
5873
}
5974

6075
/// @notice Returns the ICS20 contract address
6176
/// @return The ICS20 contract address
6277
function ics20() external view returns (address) {
63-
return StorageSlot.getAddressSlot(ICS20_STORAGE_SLOT).value;
78+
TokenConfig storage config = _getCoreConfig();
79+
return config.ics20;
80+
}
81+
82+
/// @notice Returns the Core contract address
83+
/// @return The Core contract address
84+
function core() external view returns (address) {
85+
TokenConfig storage config = _getCoreConfig();
86+
return config.core;
6487
}
6588

6689
/// @inheritdoc ERC20Upgradeable
@@ -69,17 +92,19 @@ contract MaxBTCERC20 is IMintableAndBurnable, UUPSUpgradeable, ERC20Upgradeable,
6992
}
7093

7194
/// @inheritdoc IMintableAndBurnable
72-
function mint(address mintAddress, uint256 amount) external onlyICS20 {
95+
function mint(address mintAddress, uint256 amount) external allowed {
7396
_mint(mintAddress, amount);
7497
}
7598

7699
/// @inheritdoc IMintableAndBurnable
77-
function burn(address mintAddress, uint256 amount) external onlyICS20 {
100+
function burn(address mintAddress, uint256 amount) external allowed {
78101
_burn(mintAddress, amount);
79102
}
80103

81104
/// @inheritdoc UUPSUpgradeable
82-
function _authorizeUpgrade(address) internal view override(UUPSUpgradeable) onlyOwner { }
105+
function _authorizeUpgrade(
106+
address
107+
) internal view override(UUPSUpgradeable) onlyOwner {}
83108
// solhint-disable-previous-line no-empty-blocks
84109

85110
/// @notice prevents `owner` from renouncing ownership and potentially locking assets forever
@@ -89,8 +114,12 @@ contract MaxBTCERC20 is IMintableAndBurnable, UUPSUpgradeable, ERC20Upgradeable,
89114
}
90115

91116
/// @notice Modifier to check if the caller is the ICS20 contract
92-
modifier onlyICS20() {
93-
require(_msgSender() == StorageSlot.getAddressSlot(ICS20_STORAGE_SLOT).value, CallerIsNotICS20(_msgSender()));
117+
modifier allowed() {
118+
TokenConfig storage config = _getCoreConfig();
119+
require(
120+
_msgSender() == config.ics20 || _msgSender() == config.core,
121+
CallerIsNotAllowed(_msgSender())
122+
);
94123
_;
95124
}
96125
}

src/Receiver.sol

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.8.28;
3+
4+
contract Receiver {
5+
address public publisher;
6+
uint256 private er;
7+
uint256 private ts;
8+
9+
event PublisherUpdated(
10+
address indexed oldPublisher,
11+
address indexed newPublisher
12+
);
13+
14+
event ValuesPublished(uint256 er, uint256 ts);
15+
16+
error NotPublisher();
17+
18+
constructor(address _publisher) {
19+
require(_publisher != address(0), "zero publisher");
20+
publisher = _publisher;
21+
}
22+
23+
modifier onlyPublisher() {
24+
if (msg.sender != publisher) revert NotPublisher();
25+
_;
26+
}
27+
28+
function setPublisher(address newPublisher) external onlyPublisher {
29+
require(newPublisher != address(0), "zero addr");
30+
emit PublisherUpdated(publisher, newPublisher);
31+
publisher = newPublisher;
32+
}
33+
34+
function publish(uint256 newEr, uint256 newTs) external onlyPublisher {
35+
er = newEr;
36+
ts = newTs;
37+
emit ValuesPublished(newEr, newTs);
38+
}
39+
40+
function getLatest() external view returns (uint256 _er, uint256 _ts) {
41+
return (er, ts);
42+
}
43+
}

0 commit comments

Comments
 (0)