With rise of viable "E"VM solutions directly on Bitcoin there are a few things to consider. These Virtual Machine networks on Bitcoin are sure to have their own tokens. Since these VM Bitcoin networks are adoping the EVM aka Ethereum Virtual Machine they will have "EVM" style tokens, aka ERC20, ERC721, etc will have unique programmability capabilities. These tokens will be more of "Smart Bitcoin Tokens" compared to the existing BRC20 tokens which are made to be very simple and non programmable. The Smart Bitcoin Tokens will need to have a new identity. And let's face it, "B"RC20 is already taken so we wanted to define them as what they are, Smart Bitcoin Tokens or SBTs. The term maybe inspired in branding is actually very discriptive of this new asset class that is starting to mature in Bitcoin.
This is a standard for creating and operating Smart Bitcoin Tokens (SBTs) that are interoperable across the various Virtual Machine (aka EVM) compatible solutions being built for Bitcoin, including sovereign Bitcoin rollups. By standardizing SBTs upfront, it ensures seamless user experiences and interconnectivity as Bitcoin's DeFi, tokenization, and Web3 ecosystem rapidly evolves. Widespread SBT adoption aims to help achieve at least 10% of Bitcoin's market capitalization as total value locked across these emerging platforms by the year 2026.
Multiple projects are creating EVM-compatible environments for Bitcoin that allow for programmable smart contracts, tokenization, DeFi primitives, and Web3 applications. There are plenty of examples of Bitcoin EVMs and Soveriegn rollups as of today. While incredibly promising, currently each of these platforms has their own disparate token standards and no clear interoperability between them. This fragmentation severely hinders user experiences, token mobility, liquidity, and ultimately the proliferation of these solutions. By standardizing around Smart Bitcoin Tokens (SBTs) early on, we can ensure future interconnectivity as these various Bitcoin EVM and rollup platforms emerge and mature. This provides a cohesive token layer that any dApp or protocol deployed in this ecosystem can easily integrate with out of the box.
Smart Bitcoin Tokens (SBTs) are highly compatible tokens that can be deployed permissionlessly on any Bitcoin EVM or sovereign rollup solution. SBTs fully inherit all current and future EIP standards. Any smart contract system being built for Bitcoin MUST include full support for deploying SBT contracts compiled from the Solidity, or Cairo smart contract codebase and adhering to published ABI specifications. Beyond ERC compliance, SBT contracts can integrate one or more of the following extensions:
Synthetic Assets (RWAs on Bitcoin) Inscriptions (EVM inscriptions that can hold actual native bitcoin assets). Fungible (e.g. coins, stablecoins) Non-Fungible (e.g. collectibles, gaming items) Semi-Fungible/Hybrid Dynamic (e.g. properties can change based on rules) Recursive Tokens (tokens that contain other tokens) More TBA
Mint/Burn rules and schedules Token Metadata (e.g. name, symbol, URIs, etc.) Native Staking and Rewards Vesting Schedules Governance/Voting
While the core token implementation will be common across all Bitcoin EVM/rollup solutions for full composability, each platform will be responsible for implementing trust minimized two-way bridges to enable SBTs to flow between them in the future. Potential bridging mechanisms could include cross-chain validation through merged logs, SPV proofs, fraud proofs, etc. However, the initial focus is establishing the SBT standard specification first.
Bitcoin is the most decentralized, secure, and dominant blockchain protocol. However, its limited programmability has prevented it from participating in the explosion of innovation happening in smart contract platforms, tokenization, DeFi, and Web3. The rise of Bitcoin EVM and sovereign rollup solutions is aiming to change this by bringing full smart contract capabilities to the Bitcoin ecosystem. Standardizing around SBTs as the native token layer provides the following key benefits:
Projects can effortlessly create tokens that work seamlessly across all Bitcoin EVM/rollup platforms out of the box. This fosters composability between protocols.
User Experience: By having a standard format users can easily bridge, split, combine, and interact with tokens across the entire Bitcoin ecosystem using a common wallet and asset model.
A unified token pool allows for incentivized liquidity provisioning, decentralized exchange support, lending/borrowing markets, and other DeFi primitives to flourish.
The SBT layer could potentially facilitate secure value transfer between layer 1, layer 2 solutions like Lightning, sidechains like Drivechains, and parallel chains once bridging is implemented.
SBTs maintain the core ethos of Bitcoin as a decentralized, trust minimized, permission-less system for value exchange.
A standard paves the way for attracting significant value creation, app development, and overall adoption across all Bitcoin EVM/rollup platforms.
With the rise of seamless EVM environments on Bitcoin, now is the ideal time to establish SBTs as the de facto token standard for this emerging ecosystem. This proactive step will grease the wheels for exponential growth and put Bitcoin on the path to securing 10% of its own market cap as total value secured across these new platforms.
(Reference Implementations and Share standards - Example SBT smart contracts, token bridges, wallets, explorers, etc.)
The following are sample test vectors that implementations of the SBT standard must be agreed upon and thereafter passed by projects to qualify as a SBT Ecosystem project: [These are just examples, we want to open the final standard up to community concensus via github submissions. Please contact us via the Smart Bitcoin Repo on Github if you would like to contribute to the SBT Standards].
-
Fungible SBT Deployment
- Contract: [FungibleSBT.sol]
- Constructor Parameters:
- Name: "Test Token"
- Symbol: "TEST"
- Decimals: 18
- Initial Supply: "INSERT SUPPLY AMOUNT HERE"
- Expected:
- totalSupply == 1000000000000000000000
- balanceOf(deployer) == 1000000000000000000000
-
Non-Fungible SBT Deployment
- Contract: [NonFungibleSBT.sol]
- Constructor Parameters:
- Name: "Test NFT"
- Symbol: "TESTNFT"
- Expected:
- Initial token IDs minted to deployer: [1, 2, 3, 4, 5]
-
Semi-Fungible SBT Deployment
- Contract: [SemiFungibleSBT.sol]
- Constructor Parameters:
- Name: "Test Semi-Fungible"
- Symbol: "TESTSFR"
- Initial Properties:
- Property 1 Key: "Gender" Values: ["Male", "Female"]
- Property 2 Key: "Age" Values: [18, 19, 20, 21]
- Expected:
- totalSupply == 0
- Property combinations can be uniquely minted
-
Approve/TransferFrom Fungible SBT
- Contract: From previous fungible deployment
- Actions:
- deployer.approve(account1, 1000000000000000000)
- account1.transferFrom(deployer, account2, 500000000000000000)
- Expected:
- balanceOf(deployer) == 999500000000000000000
- balanceOf(account1) == 0
- balanceOf(account2) == 500000000000000000
-
Safe Transfer NFT
- Contract: From previous non-fungible deployment
- Actions:
- deployer.safeTransferFrom(deployer, account1, 1)
- Expected:
- account1 owns NFT with ID 1
-
Batch Mint/Transfer Semi-Fungible
- Contract: From previous semi-fungible deployment
- Actions:
- deployer.batchMint(["Male", 18], ["Male", 19], ["Female", 20])
- deployer.batchTransferFrom(deployer, account1, [0, 1, 2])
- Expected:
- totalSupply == 3
- account1 owns tokens with ["Male", 18], ["Male", 19], ["Female", 20"] properties
-
Staking/Rewards
- Contract: [StakingRewardsSBT.sol]
- Preconditions:
- account1 has 1000 tokens
- staking/reward parameters set
- Actions:
- account1.stake(500)
- Wait N blocks
- Expected:
- account1 rewards balance increased based on staking amount/time
-
Governance/Voting
- Contract: [GovSBT.sol]
- Preconditions:
- Accounts own governance tokens based on supply%
- Proposal: "Change Token Name"
- Actions:
- accounts.castVotes(For/Against proposal)
- Expected:
- Votes tallied correctly based on token weights
- Proposal is accepted/rejected based on voting rules
-
Vesting Schedule
- Contract: [VestedSBT.sol]
- Preconditions:
- Vesting schedule established
- Actions:
- Fast forward clock N blocks/epochs
- Expected:
- Correct portion of tokens are released from vesting based on schedule
The testing suite should be comprehensive in covering all areas of the specification including token deployments, basic token operations (transfer, approval, etc.), implementation of extension functionality like staking/voting, and verifying proper behavior across the supported token types.
As programmable tokens with advanced functionality, SBTs introduce additional security risks that must be carefully evaluated and mitigated. All implementations of the SBT standard should undergo rigorous testing and auditing. At a minimum, the following security aspects need to be addressed:
Since SBTs are based on smart contracts, they inherit the security risks and potential vulnerabilities associated with smart contract systems, including:
- Reentrancy attacks
- Integer overflows/underflows
- Front-running attacks
- Unhandled exceptions/failed sends
- Access control exploits
- Oracle manipulation
- Composability risks
Here's what we've done to address the vulnerabilities: Functions to include in most if not all your SBTs and Smart Bitcoin Apps.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract SecureSBT is ReentrancyGuard, Ownable {
using SafeMath for uint256;
mapping(address => uint256) public balances;
// Reentrancy guard
function withdrawBalance() external nonReentrant {
uint256 balance = balances[msg.sender];
require(balance > 0, "Insufficient balance");
balances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: balance}("");
require(success, "Transfer failed");
}
// Safe math library
function unsafeIncrement(uint256 a) public pure returns (uint256) {
return a.add(1);
}
function unsafeDecrement(uint256 a) public pure returns (uint256) {
return a.sub(1, "Underflow");
}
// Commit-reveal pattern
uint256 public constant MAX_SUPPLY = 1000;
mapping(address => uint256) public balances;
mapping(address => bytes32) public commitments;
function commit(bytes32 commitment) external {
commitments[msg.sender] = commitment;
}
function mint(uint256 amount, bytes32 r, bytes8 s, uint256 v) external {
require(totalSupply().add(amount) <= MAX_SUPPLY, "Max supply reached");
bytes32 commitment = keccak256(abi.encodePacked(msg.sender, amount));
require(commitment == commitments[msg.sender], "Invalid commitment");
balances[msg.sender] = balances[msg.sender].add(amount);
delete commitments[msg.sender];
}
function totalSupply() public view returns (uint256) {
uint256 total = 0;
for (uint256 i = 0; i < balances.length; i++) {
total = total.add(balances[i]);
}
return total;
}
// Checked transfer
function transferFrom(
address sender,
address recipient,
uint256 amount
) public returns (bool) {
if (balances[sender] < amount) {
return false;
}
balances[sender] = balances[sender].sub(amount);
balances[recipient] = balances[recipient].add(amount);
return true;
}
// Role-based access control
mapping(address => bool) public admins;
modifier onlyAdmin() {
require(admins[msg.sender], "Not an admin");
_;
}
function addAdmin(address admin) external onlyOwner {
admins[admin] = true;
}
function doAdminThing(uint256 value) external onlyAdmin {
// Do something sensitive...
}
// Decentralized oracle
mapping(bytes32 => uint256) public exchangeRates;
uint256 public oracleThreshold;
mapping(address => bool) public oracles;
function setExchangeRate(bytes32 key, uint256 rate) external {
require(oracles[msg.sender], "Not an oracle");
exchangeRates[key] = rate;
}
function performTrade(bytes32 key, uint256 amount) external {
uint256 totalOracleCount;
uint256 aggregatedRate;
for (uint256 i = 0; i < oracles.length; i++) {
if (exchangeRates[key] > 0) {
totalOracleCount++;
aggregatedRate = aggregatedRate.add(exchangeRates[key]);
}
}
require(totalOracleCount >= oracleThreshold, "Insufficient oracles");
uint256 averageRate = aggregatedRate.div(totalOracleCount);
// Trade amount using averageRate
}
}
Here's what we've done to address these potential problems:
Reentrancy: We've imported the ReentrancyGuard contract from OpenZeppelin and used the nonReentrant modifier to prevent reentrancy attacks. Integer Overflow/Underflow: We've imported the SafeMath library from OpenZeppelin and used its safe math functions add and sub to prevent over/underflows. Front-running: We've implemented a commit-reveal pattern where users first commit a hash of their mint request, and then reveal the actual mint amount, preventing front-running. Unhandled Exceptions: We've properly checked and handled the return value of the balance check in the transferFrom function. Access Control: We've imported the Ownable contract from OpenZeppelin and implemented role-based access control (RBAC) for admin functions. Oracle Manipulation: Instead of relying on a single trusted oracle, we've implemented a decentralized oracle system where multiple oracles can submit exchange rates, and the contract calculates the average rate based on a configurable threshold of oracle responses.
Additionally, we've followed best practices like using the latest Solidity version, adhering to the OpenZeppelin security guidelines, and leveraging well-audited libraries and contracts. Note that this is just one approach to securing SBTs, and in practice, it's essential to conduct comprehensive security audits, formal verification, and rigorous testing to ensure the safety and correctness of the implementation.
Formal verification techniques like static analysis, symbolic execution, model checking, and security-focused coding patterns should be employed. Popular smart contract auditing tools like Slither, Manticore, and Echidna can be leveraged.
Ensuring the integrity of the SBT total supply is critical. Mitigations must be in place to prevent illegal token minting or burning by malicious actors. Some techniques:
- Capping total supply at mint
- Careful access control on minting functions
- Non-reentrant, well-analyzed mint/burn logic
- Automated unit tests for minting edge cases
- Monitoring of total supply changes
Here are examples of the Code for these functions:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SupplyIntegritySBT is Ownable, ReentrancyGuard {
uint256 public constant MAX_SUPPLY = 1000000; // Total supply cap
uint256 public totalSupply;
mapping(address => uint256) public balances;
event Transfer(address indexed from, address indexed to, uint256 value);
event Mint(address indexed to, uint256 value);
event Burn(address indexed from, uint256 value);
modifier supplyIntegrity(uint256 amount) {
require(
totalSupply + amount <= MAX_SUPPLY,
"SupplyIntegritySBT: Max supply exceeded"
);
_;
}
// Careful access control on minting
function mint(address to, uint256 amount)
external
onlyOwner
nonReentrant
supplyIntegrity(amount)
{
balances[to] += amount;
totalSupply += amount;
emit Mint(to, amount);
}
// Non-reentrant, well-analyzed burn logic
function burn(uint256 amount) external nonReentrant {
require(
balances[msg.sender] >= amount,
"SupplyIntegritySBT: Insufficient balance"
);
balances[msg.sender] -= amount;
totalSupply -= amount;
emit Burn(msg.sender, amount);
}
// Transfer with supply integrity checks
function transfer(address to, uint256 amount)
external
nonReentrant
supplyIntegrity(amount)
{
require(
balances[msg.sender] >= amount,
"SupplyIntegritySBT: Insufficient balance"
);
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
}
}
Here's what the above code does in detail to mitigate the integrity of SBTs, aka the tokens that adhere to the standards: Capping Total Supply: We've defined a constant MAX_SUPPLY that serves as the maximum total supply of tokens. Careful Access Control on Minting: The mint function is restricted to only be callable by the contract owner using the onlyOwner modifier from OpenZeppelin. Non-reentrant, Well-analyzed Mint/Burn Logic: Both the mint and burn functions are protected against reentrancy using the nonReentrant modifier from OpenZeppelin. The logic for updating balances and total supply is straightforward and has been carefully analyzed. Automated Unit Tests for Minting Edge Cases: While not shown here, it's essential to have a comprehensive suite of unit tests that cover various edge cases for minting, burning, and transferring tokens, ensuring the supply integrity invariants are upheld. Monitoring of Total Supply Changes: We've included events (Mint, Burn, and Transfer) that emit whenever the total supply or individual balances change. These events can be monitored and tracked to detect any anomalies or unauthorized changes to the token supply.
Additionally, we've included a supplyIntegrity modifier that checks if the requested mint or transfer amount would exceed the maximum supply. This modifier is applied to the mint and transfer functions to ensure the total supply cap is never breached. By combining these techniques, we've put robust safeguards in place to maintain the integrity of the SBT's total supply and prevent illegal minting or burning by malicious actors. Note that this is just one implementation example, and in practice, it's crucial to conduct thorough security audits, formal verification, and rigorous testing to ensure the correctness and safety of the supply integrity mechanisms.
For non-fungible or semi-fungible tokens, off-chain metadata stored at URIs could be a vector for exploitation if not secured properly:
- IPFS/Decentralized storage recommended
- URI signature/authentication mechanisms
- URI Immutability and persistence guarantees
Here's how that looks like in code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract SecureNFTSBT is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
mapping(uint256 => bytes32) public tokenMetadataHashes;
mapping(bytes32 => bool) public validMetadataHashes;
address public metadataAuthority;
constructor(address _metadataAuthority) ERC721("SecureNFTSBT", "SNFT") {
metadataAuthority = _metadataAuthority;
}
function safeMint(string memory metadataURI, bytes memory signature)
public
{
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(msg.sender, tokenId);
bytes32 metadataHash = keccak256(abi.encodePacked(metadataURI));
tokenMetadataHashes[tokenId] = metadataHash;
require(
isValidMetadataURI(metadataURI, signature),
"SecureNFTSBT: Invalid metadata URI"
);
_setTokenURI(tokenId, metadataURI);
validMetadataHashes[metadataHash] = true;
}
function isValidMetadataURI(
string memory metadataURI,
bytes memory signature
) public view returns (bool) {
bytes32 hash = keccak256(abi.encodePacked(metadataURI));
bytes32 messageDigest = keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
);
return ECDSA.recover(messageDigest, signature) == metadataAuthority;
}
function updateMetadataURI(uint256 tokenId, string memory newURI) public {
require(
_isApprovedOrOwner(msg.sender, tokenId),
"SecureNFTSBT: Caller is not owner nor approved"
);
bytes32 newMetadataHash = keccak256(abi.encodePacked(newURI));
require(
validMetadataHashes[newMetadataHash],
"SecureNFTSBT: Invalid metadata URI"
);
_setTokenURI(tokenId, newURI);
tokenMetadataHashes[tokenId] = newMetadataHash;
}
}
Here's how we've addressed the token metadata security concerns:
Decentralized Storage:
While not explicitly shown in the code, it's recommended to store the metadata (e.g., JSON files containing token metadata) on a decentralized storage solution like IPFS. This ensures the metadata is not controlled by a single entity and is resistant to censorship.
URI Signature/Authentication Mechanisms:
We've introduced a metadataAuthority address that is responsible for signing and authenticating valid metadata URIs. The safeMint function requires a signature along with the metadata URI. The isValidMetadataURI function verifies the signature using ECDSA recover, ensuring only the authorized metadataAuthority can approve metadata URIs. This signature mechanism prevents unauthorized parties from minting tokens with malicious metadata URIs.
URI Immutability and Persistence Guarantees:
Each token's metadata URI is stored in a mapping tokenMetadataHashes as a hash of the URI. Additionally, we maintain a set of validMetadataHashes that represents all approved metadata URI hashes. When updating a token's metadata URI (updateMetadataURI), the new URI hash must be present in the validMetadataHashes set, ensuring only approved metadata URIs can be associated with tokens. This approach guarantees the immutability and persistence of token metadata, as only approved URIs can be used, and they cannot be changed to arbitrary URIs after minting.
By combining these techniques, we've implemented robust security measures to protect the integrity and authenticity of token metadata for non-fungible and semi-fungible SBTs. Note that this is just one possible implementation, and in practice, you may need to consider additional factors such as metadata update mechanisms, metadata storage costs, and scalability concerns. Additionally, it's crucial to conduct thorough security audits, formal verification, and rigorous testing to ensure the correctness and safety of the metadata security mechanisms.
SBTs may integrate with other smart contracts like oracles, lending protocols, exchanges, etc. Secure integration patterns should be followed:
- Isolating unsafe code with program analysis
- Trust minimized, economic integration security models
- Defensive programming against composability risks
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
// Isolated Oracle Contract
contract PriceOracle {
mapping(address => uint256) public prices;
function setPrice(address token, uint256 price) external {
prices[token] = price;
}
}
// SBT Contract
contract SecureSBT is IERC20, ReentrancyGuard {
mapping(address => uint256) public balances;
uint256 public totalSupply;
PriceOracle public immutable priceOracle;
constructor(address _priceOracle) {
priceOracle = PriceOracle(_priceOracle);
}
// Economic integration with lending protocol
function deposit(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "SecureSBT: Insufficient balance");
balances[msg.sender] -= amount;
// Trust minimized integration using price oracle
uint256 collateralAmount = amount * priceOracle.prices(address(this));
// Deposit collateralAmount into lending protocol
}
// Defensive programming against composability risks
function transfer(address recipient, uint256 amount) external nonReentrant returns (bool) {
if (balances[msg.sender] < amount) {
return false;
}
balances[msg.sender] -= amount;
updateBalance(recipient, amount);
return true;
}
function updateBalance(address account, uint256 amount) private {
balances[account] += amount;
totalSupply += amount;
// Emit Transfer event
}
// Other ERC20 functions...
}
While not initially in scope, once bridges are implemented between SBT ecosystems, their security is paramount and all need:
- Formal verification of bridge contracts
- Robust challenge/dispute resolution mechanisms
- Sufficient liquidity for bridge collateralization
- Economic incentives aligned for validators
here's an example implementation that demonstrates secure bridging between SBT ecosystems, addressing the suggested security considerations:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
// SBT Contract
contract SBT is IERC20, ReentrancyGuard {
mapping(address => uint256) public balances;
uint256 public totalSupply;
// Other SBT functions...
function burn(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "SBT: Insufficient balance");
balances[msg.sender] -= amount;
totalSupply -= amount;
// Emit burn event
}
}
// Bridge Contract
contract SBTBridge is ReentrancyGuard {
SBT public token;
uint256 public chainId;
mapping(bytes32 => bool) public processedBurns;
event Burn(bytes32 burnHash, uint256 amount);
event Mint(address recipient, uint256 amount);
constructor(address _token, uint256 _chainId) {
token = SBT(_token);
chainId = _chainId;
}
// Formal verification of bridge contracts
// (Assume this contract has been formally verified)
// Robust challenge/dispute resolution mechanisms
function challengeBurn(bytes32 burnHash, bytes32 expectedBurnHash) external {
require(processedBurns[burnHash], "SBTBridge: Burn not processed");
require(burnHash != expectedBurnHash, "SBTBridge: Invalid burn challenge");
// Initiate dispute resolution process
}
// Sufficient liquidity for bridge collateralization
function burn(uint256 amount, bytes32 burnHash) external nonReentrant {
require(!processedBurns[burnHash], "SBTBridge: Burn already processed");
token.burn(amount);
processedBurns[burnHash] = true;
emit Burn(burnHash, amount);
// Lock collateral for burn
}
function mint(address recipient, uint256 amount, bytes32 burnHash) external nonReentrant {
require(processedBurns[burnHash], "SBTBridge: Burn not processed");
// Unlock collateral for burn
balances[recipient] += amount;
totalSupply += amount;
emit Mint(recipient, amount);
}
// Economic incentives aligned for validators
mapping(address => uint256) public validatorStakes;
uint256 public minimumStake;
function stakeValidator() external payable {
require(msg.value >= minimumStake, "SBTBridge: Insufficient stake");
validatorStakes[msg.sender] += msg.value;
// Distribute validator rewards
}
}
Here's how we've addressed the security considerations for bridges between SBT ecosystems:
Formal Verification of Bridge Contracts.
It's crucial to formally verify the bridge contracts using techniques like static analysis, symbolic execution, model checking, and formal verification tools like Certora, Manticore, or Echidna. Formal verification helps ensure the correctness of the bridge contracts and identify potential vulnerabilities or edge cases before deployment.
Creating functions for the purpose of formal verification can be a complex task because it often involves building functions with specific properties that can be mathematically proven to be correct. Formal verification tools like Certora, Manticore, or Echidna can then be used to check these properties against the code.
Let's create an example of a simple bridge contract with functions that can be candidates for formal verification:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title ExampleBridgeContract
* @dev This is an example of a bridge contract where formal verification can be applied.
*/
contract ExampleBridgeContract {
// Event to log the transfer of tokens to another blockchain.
event TransferInitiated(address indexed from, uint256 amount, uint256 targetChainId);
// Event to log the receipt of tokens from another blockchain.
event TransferCompleted(address indexed to, uint256 amount, uint256 sourceChainId);
// This mapping could represent the balances of users in the bridge.
mapping(address => uint256) public balances;
/**
* @notice Initiates a transfer from the current blockchain to a target blockchain.
* @param amount The amount of tokens to transfer.
* @param targetChainId The ID of the target blockchain where the tokens should be received.
*/
function initiateTransfer(uint256 amount, uint256 targetChainId) external {
require(balances[msg.sender] >= amount, "Insufficient balance to transfer");
// Lock the tokens in the bridge contract.
balances[msg.sender] -= amount;
emit TransferInitiated(msg.sender, amount, targetChainId);
// Further implementation details...
}
/**
* @notice Completes a transfer from a source blockchain to the current blockchain.
* @param to The address where the tokens should be credited.
* @param amount The amount of tokens to be received.
* @param sourceChainId The ID of the source blockchain from where the tokens were sent.
*/
function completeTransfer(address to, uint256 amount, uint256 sourceChainId) external {
// This would involve validation of proof of burn or equivalent from the source chain.
// For example, a cryptographic proof that the tokens were locked/burnt on the source chain.
// Assuming the proof is valid, the tokens are unlocked/credited to the 'to' address.
balances[to] += amount;
emit TransferCompleted(to, amount, sourceChainId);
// Further implementation details...
}
// Other functions related to the bridge operations...
}
In the context of formal verification, you would define properties and invariants that must hold true for your contract. For the initiateTransfer function, an invariant might be that the sum of all user balances must not change as a result of a transfer. For the completeTransfer function, you might want to prove that tokens can only be minted if a valid proof from the source chain is provided.
These properties would then be specified in a formal language, and a formal verification tool would be used to prove that the properties hold for all possible executions of the contract. If the formal verification tool finds a case where the property does not hold, it can provide a counterexample that shows how the invariant can be violated, which is invaluable for debugging and improving the contract.
The SBTBridge contract includes a challengeBurn function that allows challenging a processed burn transaction. If the provided burnHash doesn't match the expected expectedBurnHash, it triggers a dispute resolution process. The dispute resolution process can involve mechanisms such as temporary freezing of funds, governance voting, or arbitration to resolve the dispute.
For example, we can create a 'challengeBurn' and 'resolveDispute'. The challengeBurn function is used to initiate a challenge against a burn transaction. If a discrepancy is found, the resolveDispute function can be invoked, which would typically be restricted to an arbitrator role or governed by a consensus mechanism. Here’s how they might look:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title RobustChallengeDisputeBridge
* @dev This contract demonstrates a mechanism for challenging and resolving disputes for burn transactions in a bridge.
*/
contract RobustChallengeDisputeBridge {
event Burn(address indexed from, uint256 amount, bytes32 indexed burnHash);
event Challenge(address indexed challenger, bytes32 indexed burnHash);
event DisputeResolved(bytes32 indexed burnHash, bool burnValid);
// Storage for burn transactions; true if valid, false if under dispute
mapping(bytes32 => bool) public burnTransactions;
// Storage to hold the details of a challenge
mapping(bytes32 => address) public challenges;
// The arbitrator role could be an address or a smart contract that has the authority to resolve disputes
address public arbitrator;
modifier onlyArbitrator() {
require(msg.sender == arbitrator, "Caller is not the arbitrator");
_;
}
constructor(address _arbitrator) {
arbitrator = _arbitrator;
}
/**
* @notice Records a burn transaction.
* @param amount The amount of tokens burned.
* @param burnHash A unique hash representing the burn transaction.
*/
function burn(uint256 amount, bytes32 burnHash) external {
require(!burnTransactions[burnHash], "Burn already recorded");
burnTransactions[burnHash] = true;
emit Burn(msg.sender, amount, burnHash);
}
/**
* @notice Allows a user to challenge a burn transaction.
* @param burnHash The hash of the burn transaction being challenged.
*/
function challengeBurn(bytes32 burnHash) external {
require(burnTransactions[burnHash], "Burn transaction not found");
burnTransactions[burnHash] = false;
challenges[burnHash] = msg.sender;
emit Challenge(msg.sender, burnHash);
}
/**
* @notice Resolves a dispute over a burn transaction. Only callable by the arbitrator.
* @param burnHash The hash of the burn transaction being disputed.
* @param burnValid A boolean representing the outcome of the dispute.
*/
function resolveDispute(bytes32 burnHash, bool burnValid) external onlyArbitrator {
require(challenges[burnHash] != address(0), "Dispute not found");
burnTransactions[burnHash] = burnValid;
emit DisputeResolved(burnHash, burnValid);
// Optionally, clean up the challenge entry
delete challenges[burnHash];
}
// Additional functions related to the bridge operations could be added here...
// Function to update the arbitrator address
function updateArbitrator(address newArbitrator) external onlyArbitrator {
require(newArbitrator != address(0), "Invalid new arbitrator address");
arbitrator = newArbitrator;
}
}
In the above code:
The resolveDispute function allows the arbitrator to either confirm the validity of the burn transaction or declare it invalid based on the evidence provided in the dispute resolution process. The onlyArbitrator modifier ensures that only the designated arbitrator can call the resolveDispute function. After resolving a dispute, the challenges entry associated with the burnHash can be deleted to free up storage and potentially refund some gas. An additional function updateArbitrator is provided to change the arbitrator, in case there's a need to transfer arbitration responsibilities to a different address or contract. This function is also protected by the onlyArbitrator modifier to prevent unauthorized changes. Please note that for a real-world implementation, additional considerations such as a more decentralized arbitration mechanism, specific logic for how disputes are adjudicated, and the use of cryptographic proofs may be necessary to ensure robustness and security of the dispute resolution process.
The burn function in the SBTBridge contract locks collateral when a burn transaction is processed. This collateral serves as a guarantee that the corresponding mint operation will be executed on the target chain. The collateral is unlocked when the mint function is called with the correct burnHash, ensuring sufficient liquidity for the bridging operation.
// SBTBridge contract
// Mapping to store the collateral for each burn transaction
mapping(bytes32 => uint256) public collateralLocked;
// Function to lock collateral when a burn transaction is processed
function burn(uint256 _amount, bytes32 _burnHash) public {
// Require that the caller has enough tokens to burn
require(balanceOf(msg.sender) >= _amount, "Insufficient tokens to burn");
// Lock the collateral for the burn transaction
collateralLocked[_burnHash] = _amount;
// Burn the tokens
_burn(msg.sender, _amount);
// Emit an event to notify that the collateral has been locked
emit CollateralLocked(_burnHash, _amount);
}
// Function to unlock the collateral when the mint function is called
function mint(uint256 _amount, bytes32 _burnHash) public {
// Require that the collateral has been locked for this burn transaction
require(collateralLocked[_burnHash] > 0, "No collateral locked for this burn transaction");
// Unlock the collateral
uint256 collateralAmount = collateralLocked[_burnHash];
collateralLocked[_burnHash] = 0;
// Mint the tokens
_mint(msg.sender, _amount);
// Emit an event to notify that the collateral has been unlocked
emit CollateralUnlocked(_burnHash, collateralAmount);
}
// Events
event CollateralLocked(bytes32 indexed burnHash, uint256 amount);
event CollateralUnlocked(bytes32 indexed burnHash, uint256 amount);
In the above code the burn function locks the collateral for a burn transaction by storing the collateral amount in the collateralLocked mapping, using the _burnHash as the key. The mint function then checks if the collateral has been locked for the corresponding burn transaction and unlocks the collateral, minting the tokens to the recipient.
The CollateralLocked and CollateralUnlocked events are emitted to notify the system that the collateral has been locked or unlocked, respectively.
Note that this is a simplified example and you may need to add additional checks, validations, and functionality depending on the specific requirements of your project.
The SBTBridge contract includes a mechanism for validators to stake a minimum amount (minimumStake) to participate in the bridge validation process. The stakeValidator function allows validators to stake their tokens and receive rewards for participating in the validation process. This incentivizes validators to act honestly and follow the bridging protocol rules, as their stake is at risk if they misbehave.
Here's the SBT Standard example code for this:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
contract SBTBridge is ERC20, Ownable, Pausable, Initializable {
// Mapping to store the collateral for each burn transaction
mapping(bytes32 => uint256) public collateralLocked;
// Mapping to store the validator stakes
mapping(address => uint256) public validatorStakes;
// Minimum stake required for a validator
uint256 public minimumStake = 1000; // Example value, adjust as needed
function initialize(string memory name, string memory symbol, uint256 initialSupply) public initializer {
__ERC20_init(name, symbol);
__Ownable_init();
__Pausable_init();
_mint(owner(), initialSupply);
}
// Function to lock collateral when a burn transaction is processed
function burn(uint256 _amount, bytes32 _burnHash) public whenNotPaused {
// Require that the caller has enough tokens to burn
require(balanceOf(msg.sender) >= _amount, "Insufficient tokens to burn");
// Lock the collateral for the burn transaction
collateralLocked[_burnHash] = _amount;
// Burn the tokens
_burn(msg.sender, _amount);
// Emit an event to notify that the collateral has been locked
emit CollateralLocked(_burnHash, _amount);
}
// Function to unlock the collateral when the mint function is called
function mint(uint256 _amount, bytes32 _burnHash) public whenNotPaused {
// Require that the collateral has been locked for this burn transaction
require(collateralLocked[_burnHash] > 0, "No collateral locked for this burn transaction");
// Unlock the collateral
uint256 collateralAmount = collateralLocked[_burnHash];
collateralLocked[_burnHash] = 0;
// Mint the tokens
_mint(msg.sender, _amount);
// Emit an event to notify that the collateral has been unlocked
emit CollateralUnlocked(_burnHash, collateralAmount);
}
// Function to allow validators to stake their tokens
function stakeValidator(uint256 _amount) public whenNotPaused {
// Require that the validator is staking at least the minimum amount
require(_amount >= minimumStake, "Stake amount must be at least the minimum stake");
// Transfer the tokens from the validator's account to the contract
_transfer(msg.sender, address(this), _amount);
// Update the validator's stake
validatorStakes[msg.sender] += _amount;
// Emit an event to notify that a validator has staked their tokens
emit ValidatorStaked(msg.sender, _amount);
}
// Events
event CollateralLocked(bytes32 indexed burnHash, uint256 amount);
event CollateralUnlocked(bytes32 indexed burnHash, uint256 amount);
event ValidatorStaked(address indexed validator, uint256 amount);
}
In the Above Code: Created mapping validatorStakes to store the stakes of each validator. Created a stakeValidator function that allows validators to stake their tokens and receive rewards for participating in the validation process. Implemented a minimumStake variable to ensure that validators are staking at least the minimum required amount. Emitted a ValidatorStaked event to notify the system that a validator has staked their tokens. This setup ensures that validators have a financial incentive to act honestly and follow the bridging protocol rules, as their stake is at risk if they misbehave.
Note that this is a simplified example and you may need to add additional checks, validations, and functionality depending on the specific requirements of your project.
Finally, SBT implementations should adhere to general blockchain security best practices:
- Comprehensive test suite with high coverage
- Adherence to latest Solidity/EVM security guidelines
- Continuous security monitoring and updates
- Mainnet deployment and upgrade processes
- Responsible disclosure and bug bounty programs\
By implementing these security measures, the bridge between SBT ecosystems can operate in a secure and trust minimized manner, mitigating risks associated with cross-chain value transfers. However, it's important to note that this is just an example implementation, and in practice, additional security considerations and rigorous auditing may be required based on the specific bridging protocol and the characteristics of the involved chains. Additionally, as new attack vectors and security threats emerge, the security patterns and best practices may need to be updated and adapted accordingly.
SBTs differ from BRC Tokens and ERC Tokens in sometimes suddle but important ways. For example: SBTs can be minted from a Smart Contract without the need for them to be demoniated in Sats Like BRC tokens. SBTs are a new Smart Token class that is directly on top of Bitcoin, the most decentralized and secure blockchain unlike ERC tokens which are on ETH POS Chain [No Shade there, I am an etherean :] SBTs have many different forms fungible, nonfungible, hybrid, dynamic, recursive, and more. SBTs have native staking capabilities on VMs (Virtual Machines) on Bitcoin. SBTs are wildly programmable unlike BRC and other Meta tokens on Bitcoin currently. SBTs offer a new path for creators and projects to launch tokens with sofisticated functions such as presales, vesting, voting, and others. SBTs are completely permissionless and on-chain. No need for external indexers.
[COMING SOON] SBT Standard Audit and Verification Systems for teams building BTC EVM compatible dapps.
What is a Token? Tokens can represent virtually anything in Bitcoin: reputation points in an online platform skills of a character in a game lottery tickets financial assets like a share in a company a fiat currency like USD an ounce of gold and more... Such a powerful feature of Bitcoin must be handled by a robust standard, right? That's exactly where the SBT-20 plays its role! This standard allows developers to build smart token applications that are interoperable with other products and services in the Bitcoin Network
The SBT-20 introduces a standard for Fungible Tokens, in other words, they have a property that makes each Token be exactly the same (in type and value) as another Token. Meaning 1 Token is and will always be equal to all the other Tokens.
- Learn about the OG EVM and concepts.
- Account
- Smart Contracts
- Token standards
The SBT-20 (Bitcoin Smart Tokens 20), builds on the ERC-20 standard proposed by Fabian Vogelsteller in November 2015, which is a Token Standard that implements an API for tokens within Smart Contracts. The SBT-20 standard expands this implementation adding smart functionality to Bitcoin tokens for all Bitcoin EVM scaling networks to adopt. Example functionalities SBT-20 provides:
- transfer tokens from one account to another
- get the current token balance of an account
- get the total supply of the token available on the network
- approve whether an amount of token from an account can be spent by a third-party account
If a Smart Contract implements the following methods and events it can be called an SBT-20 Token Contract and, once deployed, it will be responsible to keep track of the created tokens on Bitcoin.
function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)
event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
Let's see how a Standard is so important to make things simple for us to inspect any SBT-20 Token Contract on Bitcoin. We just need the Contract Application Binary Interface (ABI) to create an interface to any SBT-20 Token. As you can see below we will use a simplified ABI, to make it a low friction example.
pip install web3.py
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://cloudflare-eth.com")) [NEEDS TO BE UPDATED WITH https://cloudflare-btc.com"]
dai_token_addr = "0x6B175474E89094C44Da98b954EedeAC495271d0F" # STABLE
weth_token_addr = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" # Wrapped
acc_address = "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11" # AVOSWAP V2: STABLE 2
# This is a simplified Contract Application Binary Interface (ABI) of an ERC-20 Token Contract.
# It will expose only the methods: balanceOf(address), decimals(), symbol() and totalSupply()
simplified_abi = [
{
'inputs': [{'internalType': 'address', 'name': 'account', 'type': 'address'}],
'name': 'balanceOf',
'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}],
'stateMutability': 'view', 'type': 'function', 'constant': True
},
{
'inputs': [],
'name': 'decimals',
'outputs': [{'internalType': 'uint8', 'name': '', 'type': 'uint8'}],
'stateMutability': 'view', 'type': 'function', 'constant': True
},
{
'inputs': [],
'name': 'symbol',
'outputs': [{'internalType': 'string', 'name': '', 'type': 'string'}],
'stateMutability': 'view', 'type': 'function', 'constant': True
},
{
'inputs': [],
'name': 'totalSupply',
'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}],
'stateMutability': 'view', 'type': 'function', 'constant': True
}
]
dai_contract = w3.eth.contract(address=w3.to_checksum_address(dai_token_addr), abi=simplified_abi)
symbol = dai_contract.functions.symbol().call()
decimals = dai_contract.functions.decimals().call()
totalSupply = dai_contract.functions.totalSupply().call() / 10**decimals
addr_balance = dai_contract.functions.balanceOf(acc_address).call() / 10**decimals
# STABLE
print("===== %s =====" % symbol)
print("Total Supply:", totalSupply)
print("Addr Balance:", addr_balance)
weth_contract = w3.eth.contract(address=w3.to_checksum_address(weth_token_addr), abi=simplified_abi)
symbol = weth_contract.functions.symbol().call()
decimals = weth_contract.functions.decimals().call()
totalSupply = weth_contract.functions.totalSupply().call() / 10**decimals
addr_balance = weth_contract.functions.balanceOf(acc_address).call() / 10**decimals
# WRAPPED
print("===== %s =====" % symbol)
print("Total Supply:", totalSupply)
print("Addr Balance:", addr_balance)
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
contract Silk {
string public name = "@SmartBitcoin.cc";
string public symbol = "SBT";
string public standard = "Smart Bitcoin Token 0.5";
uint256 public totalSupply;
address public ownerOfContract;
uint256 public _userId;
uint256 constant initialSupply = 1000000 * (10**18);
address[] public holderToken;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _value
);
mapping(address => TokenHolderInfo) public tokenHolderInfos;
struct TokenHolderInfo {
uint256 _tokenId;
address _from;
address _to;
uint256 _totalToken;
bool _tokenHolder;
}
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
constructor() {
ownerOfContract = msg.sender;
balanceOf[msg.sender] = initialSupply;
totalSupply = initialSupply;
}
function inc() internal {
_userId++;
}
function transfer(address _to, uint256 _value)
public
returns (bool success)
{
require(balanceOf[msg.sender] >= _value);
inc();
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
TokenHolderInfo storage tokenHolderInfo = tokenHolderInfos[_to];
tokenHolderInfo._to = _to;
tokenHolderInfo._from = msg.sender;
tokenHolderInfo._totalToken = _value;
tokenHolderInfo._tokenHolder = true;
tokenHolderInfo._tokenId = _userId;
holderToken.push(_to);
emit Transfer(msg.sender, _to, _value);
return true;
}
function approve(address _spender, uint256 _value)
public
returns (bool success)
{
allowance[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function transferFrom(
address _from,
address _to,
uint256 _value
) public returns (bool success) {
require(_value <= balanceOf[_from]);
require(_value <= allowance[_from][msg.sender]);
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
allowance[_from][msg.sender] -= _value;
emit Transfer(_from, _to, _value);
return true;
}
function getTokenHolderData(address _address)
public
view
returns (
uint256,
address,
address,
uint256,
bool
)
{
return (
tokenHolderInfos[_address]._tokenId,
tokenHolderInfos[_address]._to,
tokenHolderInfos[_address]._from,
tokenHolderInfos[_address]._totalToken,
tokenHolderInfos[_address]._tokenHolder
);
}
function getTokenHolder() public view returns (address[] memory) {
return holderToken;
}
}
SBT721, SBT1155, SBT404 Tooling and web3/eth packages reconstruction Creating BIPs system in Repo. MEV in VM networks on Bitcoin Scaling VM networks on Bitcoin Virtual Machines on Bitcoin Usecases
Historical links related to this standard and the inspiration for it:
https://bitcoinpaper.org/bitcoin.pdf https://github.com/bitcoin/bips Original proposal from Vitalik Buterin: https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs/499c882f3ec123537fc2fccd57eaa29e6032fe4a Reddit discussion: https://www.reddit.com/r/ethereum/comments/3n8fkn/lets_talk_about_the_coin_standard/ Original Issue #20: ethereum/EIPs#20
Copyright and related rights waived via CC0.
Fabian Vogelsteller fabian@ethereum.org, Vitalik Buterin vitalik.buterin@ethereum.org, "ERC-20: Token Standard," Ethereum Improvement Proposals, no. 20, November 2015. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-20, Ryda Almogtaba admin@pickslabs.xyz, "SBT-20: Token Standard," Smart Bitcoin Improvement Proposals No. 1, March 2023. [Online serial]. Available: https://www.smartbitcoin.cc
Donations or Tips to Smart Bitcoin are welcomed: BTC/ORDINALS/RUNES
bc1p54hass6uhh8q55ucqlyn8pq00yvp4r5d7vstwxel99hawq2zcerq2jelet
MAINNET SBTs
0x0B2b5A6B723524BBD8e463246ea45FD401c0E079
bc1p54hass6uhh8q55ucqlyn8pq00yvp4r5d7vstwxel99hawq2zcerq2jelet
SIGNET BTC
tb1pf5rag20sez3rlplyec9u56n234g96q93xrk9wwddmrlh5t7a23tqca7lxv
0x1deDA121832F1B532BD90F470E76B2bdEf8C293a
SIGNET SBTS ON ANY BITCOIN EVM.
tb1pf5rag20sez3rlplyec9u56n234g96q93xrk9wwddmrlh5t7a23tqca7lxv
License The Smart Bitcoin Standards are open source under the terms of the MIT License.