Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework to the wrapped token approach #9

Merged
merged 3 commits into from
Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ build/
.env
.idea/
node_modules/

coverage/
coverage.json
2 changes: 2 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"reason-string": "off",
"no-inline-assembly": "off",
"no-complex-fallback": "off",
"avoid-low-level-calls": "off",
"no-empty-blocks": "off",
"func-visibility": ["warn", {"ignoreConstructors": true}],
"prettier/prettier": "error",
"compiler-version": ["error", "0.8.7"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@

pragma solidity 0.8.7;

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "./interfaces/IDepositContract.sol";
import "./interfaces/IERC20.sol";
import "./interfaces/IERC165.sol";
import "./interfaces/IERC677Receiver.sol";
import "./utils/EIP1967Admin.sol";
import "./utils/PausableEIP1967Admin.sol";
import "./utils/Claimable.sol";

/**
* @title StakeDepositContract
* @title SBCDepositContract
* @dev Implementation of the ERC20 ETH2.0 deposit contract.
* For the original implementation, see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
*/
contract StakeDepositContract is IDepositContract, IERC165, IERC677Receiver, EIP1967Admin, Claimable {
contract SBCDepositContract is IDepositContract, IERC165, IERC677Receiver, PausableEIP1967Admin, Claimable {
uint256 private constant DEPOSIT_CONTRACT_TREE_DEPTH = 32;
// NOTE: this also ensures `deposit_count` will fit into 64-bits
uint256 private constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1;
Expand All @@ -24,22 +23,10 @@ contract StakeDepositContract is IDepositContract, IERC165, IERC677Receiver, EIP
bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] private branch;
uint256 private deposit_count;

bool public paused;

IERC20 public immutable stake_token;
IERC20 public immutable deposit_token;

constructor(address _token) {
stake_token = IERC20(_token);
}

modifier whenNotPaused() {
require(!paused);
_;
}

function setPaused(bool _paused) external onlyAdmin {
require(_paused != paused);
paused = _paused;
deposit_token = IERC20(_token);
}

function get_deposit_root() external view override returns (bytes32) {
Expand All @@ -65,10 +52,10 @@ contract StakeDepositContract is IDepositContract, IERC165, IERC677Receiver, EIP
bytes memory withdrawal_credentials,
bytes memory signature,
bytes32 deposit_data_root,
uint256 stake_amount
uint256 deposit_amount
) external override whenNotPaused {
stake_token.transferFrom(msg.sender, address(this), stake_amount);
_deposit(pubkey, withdrawal_credentials, signature, deposit_data_root, stake_amount);
deposit_token.transferFrom(msg.sender, address(this), deposit_amount);
_deposit(pubkey, withdrawal_credentials, signature, deposit_data_root, deposit_amount);
}

function batchDeposit(
Expand All @@ -78,46 +65,46 @@ contract StakeDepositContract is IDepositContract, IERC165, IERC677Receiver, EIP
bytes32[] calldata deposit_data_roots
) external whenNotPaused {
uint256 count = deposit_data_roots.length;
require(count > 0, "BatchDeposit: You should deposit at least one validator");
require(count <= 128, "BatchDeposit: You can deposit max 128 validators at a time");
require(count > 0, "SBCDepositContract: You should deposit at least one validator");
require(count <= 128, "SBCDepositContract: You can deposit max 128 validators at a time");

require(pubkeys.length == count * 48, "BatchDeposit: Pubkey count don't match");
require(signatures.length == count * 96, "BatchDeposit: Signatures count don't match");
require(withdrawal_credentials.length == 32, "BatchDeposit: Withdrawal Credentials count don't match");
require(pubkeys.length == count * 48, "SBCDepositContract: Pubkey count don't match");
require(signatures.length == count * 96, "SBCDepositContract: Signatures count don't match");
require(withdrawal_credentials.length == 32, "SBCDepositContract: Withdrawal Credentials count don't match");

uint256 stake_amount = 32 ether;
stake_token.transferFrom(msg.sender, address(this), stake_amount * count);
uint256 deposit_amount = 32 ether;
deposit_token.transferFrom(msg.sender, address(this), deposit_amount * count);

for (uint256 i = 0; i < count; ++i) {
bytes memory pubkey = bytes(pubkeys[i * 48:(i + 1) * 48]);
bytes memory signature = bytes(signatures[i * 96:(i + 1) * 96]);

_deposit(pubkey, withdrawal_credentials, signature, deposit_data_roots[i], stake_amount);
_deposit(pubkey, withdrawal_credentials, signature, deposit_data_roots[i], deposit_amount);
}
}

function onTokenTransfer(
address,
uint256 stake_amount,
uint256 amount,
bytes calldata data
) external override whenNotPaused returns (bool) {
require(msg.sender == address(stake_token));
require(data.length % 176 == 32, "DepositContract: incorrect deposit data length");
require(msg.sender == address(deposit_token), "SBCDepositContract: not a deposit token");
require(data.length % 176 == 32, "SBCDepositContract: incorrect deposit data length");
uint256 count = data.length / 176;
require(count > 0, "BatchDeposit: You should deposit at least one validator");
uint256 stake_amount_per_deposit = stake_amount;
require(count > 0, "SBCDepositContract: You should deposit at least one validator");
uint256 deposit_amount = amount;
if (count > 1) {
require(count <= 128, "BatchDeposit: You can deposit max 128 validators at a time");
require(stake_amount == 32 ether * count, "BatchDeposit: batch deposits require 32 STAKE deposit amount");
stake_amount_per_deposit = 32 ether;
require(count <= 128, "SBCDepositContract: You can deposit max 128 validators at a time");
require(amount == 32 ether * count, "SBCDepositContract: batch deposits require 32 SBC deposit amount");
deposit_amount = 32 ether;
}

bytes memory withdrawal_credentials = data[0:32];
for (uint256 p = 32; p < data.length; p += 176) {
bytes memory pubkey = data[p:p + 48];
bytes memory signature = data[p + 48:p + 144];
bytes32 deposit_data_root = bytes32(data[p + 144:p + 176]);
_deposit(pubkey, withdrawal_credentials, signature, deposit_data_root, stake_amount_per_deposit);
_deposit(pubkey, withdrawal_credentials, signature, deposit_data_root, deposit_amount);
}
return true;
}
Expand All @@ -127,18 +114,18 @@ contract StakeDepositContract is IDepositContract, IERC165, IERC677Receiver, EIP
bytes memory withdrawal_credentials,
bytes memory signature,
bytes32 deposit_data_root,
uint256 stake_amount
uint256 deposit_amount
) internal {
// Extended ABI length checks since dynamic types are used.
require(pubkey.length == 48, "DepositContract: invalid pubkey length");
require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length");
require(signature.length == 96, "DepositContract: invalid signature length");
require(pubkey.length == 48, "SBCDepositContract: invalid pubkey length");
require(withdrawal_credentials.length == 32, "SBCDepositContract: invalid withdrawal_credentials length");
require(signature.length == 96, "SBCDepositContract: invalid signature length");

// Check deposit amount
require(stake_amount >= 1 ether, "DepositContract: deposit value too low");
require(stake_amount % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei");
uint256 deposit_amount = stake_amount / 1 gwei;
require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high");
require(deposit_amount >= 1 ether, "SBCDepositContract: deposit value too low");
require(deposit_amount % 1 gwei == 0, "SBCDepositContract: deposit value not multiple of gwei");
uint256 deposit_amount = deposit_amount / 1 gwei;
require(deposit_amount <= type(uint64).max, "SBCDepositContract: deposit value too high");

// Emit `DepositEvent` log
bytes memory amount = to_little_endian_64(uint64(deposit_amount));
Expand Down Expand Up @@ -169,11 +156,11 @@ contract StakeDepositContract is IDepositContract, IERC165, IERC677Receiver, EIP
// Verify computed and expected deposit data roots match
require(
node == deposit_data_root,
"DepositContract: reconstructed DepositData does not match supplied deposit_data_root"
"SBCDepositContract: reconstructed DepositData does not match supplied deposit_data_root"
);

// Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`)
require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full");
require(deposit_count < MAX_DEPOSIT_COUNT, "SBCDepositContract: merkle tree full");

// Add deposit data root to Merkle tree (update a single `branch` node)
deposit_count += 1;
Expand Down Expand Up @@ -203,10 +190,10 @@ contract StakeDepositContract is IDepositContract, IERC165, IERC677Receiver, EIP
* Only admin can call this method.
* Deposit-related tokens cannot be claimed.
* @param _token address of the token, if it is not provided (0x00..00), native coins will be transferred.
* @param _to address that will receive the locked tokens on this contract.
* @param _to address that will receive the locked tokens from this contract.
*/
function claimTokens(address _token, address _to) external onlyAdmin {
require(address(stake_token) != _token);
require(address(deposit_token) != _token, "SBCDepositContract: not allowed to claim deposit token");
_claimValues(_token, _to);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
pragma solidity 0.8.7;

import "./utils/EIP1967Proxy.sol";
import "./StakeDepositContract.sol";
import "./SBCDepositContract.sol";

/**
* @title StakeDepositContractProxy
* @dev Upgradeable and Claimable version of the underlying StakeDepositContract.
* @title SBCDepositContractProxy
* @dev Upgradeable version of the underlying SBCDepositContract.
*/
contract StakeDepositContractProxy is EIP1967Proxy {
contract SBCDepositContractProxy is EIP1967Proxy {
bool private paused;

uint256 private constant DEPOSIT_CONTRACT_TREE_DEPTH = 32;
// first slot from StakeDepositContract
bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] private zero_hashes;

constructor(address _admin, address _token) {
_setAdmin(_admin);
_setImplementation(address(new StakeDepositContract(_token)));
_setImplementation(address(new SBCDepositContract(_token)));

// Compute hashes in empty sparse Merkle tree
for (uint256 height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++)
Expand Down
67 changes: 67 additions & 0 deletions contracts/SBCToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: CC0-1.0

pragma solidity 0.8.7;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "./utils/Claimable.sol";
import "./utils/PausableEIP1967Admin.sol";
import "./interfaces/IERC677.sol";
import "./interfaces/IERC677Receiver.sol";

/**
* @title SBCToken
* @dev Wrapped token used for depositing into SBC.
*/
contract SBCToken is IERC677, ERC20Pausable, PausableEIP1967Admin, Claimable {
address private _minter;

constructor() ERC20("", "") {}

/**
* @dev Initialization setter for the minter address.
* Only admin can call this method.
* @param minter address of the SBCWrapper contract.
*/
function setMinter(address minter) external onlyAdmin {
require(_minter == address(0), "SBCToken: minter already set");
_minter = minter;
}

/**
* @dev Mints new tokens.
* Only configured minter is allowed to mint tokens, which should be a SBCWrapper contract.
* @param _to tokens receiver.
* @param _amount amount of tokens to mint.
*/
function mint(address _to, uint256 _amount) external {
require(_msgSender() == _minter, "SBCToken: not a minter");
_mint(_to, _amount);
}

/**
* @dev Implements the ERC677 transferAndCall standard.
* Executes a regular transfer, but calls the receiver's function to handle them in the same transaction.
* @param _to tokens receiver.
* @param _amount amount of sent tokens.
* @param _data extra data to pass to the callback function.
*/
function transferAndCall(
address _to,
uint256 _amount,
bytes calldata _data
) external override {
address sender = _msgSender();
_transfer(sender, _to, _amount);
require(IERC677Receiver(_to).onTokenTransfer(sender, _amount, _data), "SBCToken: ERC677 callback failed");
}

/**
* @dev Allows to transfer any locked token from this contract.
* Only admin can call this method.
* @param _token address of the token, if it is not provided (0x00..00), native coins will be transferred.
* @param _to address that will receive the locked tokens from this contract.
*/
function claimTokens(address _token, address _to) external onlyAdmin {
_claimValues(_token, _to);
}
}
32 changes: 32 additions & 0 deletions contracts/SBCTokenProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: CC0-1.0

pragma solidity 0.8.7;

import "./utils/EIP1967Proxy.sol";
import "./SBCToken.sol";

/**
* @title SBCTokenProxy
* @dev Upgradeable version of the underlying SBCToken.
*/
contract SBCTokenProxy is EIP1967Proxy {
mapping(address => uint256) private _balances;

mapping(address => mapping(address => uint256)) private _allowances;

uint256 private _totalSupply;

string private _name;
string private _symbol;

constructor(
address _admin,
string memory name,
string memory symbol
) {
_setAdmin(_admin);
_setImplementation(address(new SBCToken()));
_name = name;
_symbol = symbol;
}
}
Loading