Contract Name:
MintFactory
Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental SMTChecker;
import "ERC20If.sol";
import "OwnableIf.sol";
/// @title CanReclaimToken
abstract contract CanReclaimToken is OwnableIf {
function reclaimToken(ERC20If _token) external onlyOwner {
uint256 balance = _token.balanceOf((address)(this));
require(_token.transfer(_owner(), balance));
}
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental SMTChecker;
/// @title ERC20If
abstract contract ERC20If {
function totalSupply() virtual public view returns (uint256);
function balanceOf(address _who) virtual public view returns (uint256);
function transfer(address _to, uint256 _value) virtual public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
function allowance(address _owner, address _spender) virtual public view returns (uint256);
function transferFrom(address _from, address _to, uint256 _value) virtual public returns (bool);
function approve(address _spender, uint256 _value) virtual public returns (bool);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
pragma solidity ^0.7.0;
pragma experimental SMTChecker;
//SPDX-License-Identifier: MIT
/// @title MemberMgrIf
abstract contract MemberMgrIf {
function requireMerchant(address _who) virtual public view;
function requireCustodian(address _who) virtual public view;
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental SMTChecker;
//pragma experimental ABIEncoderV2;
import "Ownable.sol";
import "MTokenControllerIf.sol";
import "MintFactoryIfView.sol";
import "CanReclaimToken.sol";
/// @title MintFactory
contract MintFactory is Ownable, MintFactoryIfView, CanReclaimToken {
function getStatusString(RequestStatus status) internal pure returns (string memory) {
if (status == RequestStatus.PENDING) {
return "pending";
} else if (status == RequestStatus.CANCELED) {
return "canceled";
} else if (status == RequestStatus.APPROVED) {
return "approved";
} else if (status == RequestStatus.REJECTED) {
return "rejected";
} else {
// unreachable.
return "unknown";
}
}
function getMintRequest(uint seq) override
external
view
returns (
uint requestSeq,
address requester,
uint amount,
string memory btcAddress,
string memory btcTxId,
uint requestBlockNo,
uint confirmedBlockNo,
string memory status,
bytes32 requestHash
)
{
require(seq > 0, "seq from 1");
require(seq < mintRequests.length, "invalid seq");
Request memory request = mintRequests[seq];
string memory statusString = getStatusString(request.status);
requestSeq = seq;
requester = request.requester;
amount = request.amount;
btcAddress = request.btcAddress;
btcTxId = request.btcTxId;
requestBlockNo = request.requestBlockNo;
confirmedBlockNo = request.confirmedBlockNo;
status = statusString;
requestHash = calcRequestHash(request);
}
function getMintRequestsLength() override external view returns (uint length) {
return mintRequests.length;
}
function getBurnRequest(uint seq) override
external
view
returns (
uint requestSeq,
address requester,
uint amount,
string memory btcAddress,
string memory btcTxId,
uint requestBlockNo,
uint confirmedBlockNo,
string memory status,
bytes32 requestHash
)
{
require(seq > 0, "seq from 1");
require(seq < burnRequests.length, "invalid seq");
Request storage request = burnRequests[seq];
string memory statusString = getStatusString(request.status);
requestSeq = seq;
requester = request.requester;
amount = request.amount;
btcAddress = request.btcAddress;
btcTxId = request.btcTxId;
requestBlockNo = request.requestBlockNo;
confirmedBlockNo = request.confirmedBlockNo;
status = statusString;
requestHash = calcRequestHash(request);
}
function getBurnRequestsLength() override external view returns (uint length) {
return burnRequests.length;
}
constructor() {
controller = (MTokenControllerIf)(owner);
Request memory request = Request({
requester : (address)(0),
amount : 0,
btcAddress : "invalid.address",
btcTxId : "invalid.tx",
seq : 0,
requestBlockNo : 0,
confirmedBlockNo : 0,
status : RequestStatus.REJECTED
});
mintRequests.push(request);
burnRequests.push(request);
}
modifier onlyMerchant() {
controller.requireMerchant(msg.sender);
_;
}
modifier onlyCustodian() {
controller.requireCustodian(msg.sender);
_;
}
function compareStrings(string memory a, string memory b) internal pure returns (bool) {
if (bytes(a).length != bytes(b).length) {
return false;
}
for (uint i = 0; i < bytes(a).length; i ++) {
if (bytes(a)[i] != bytes(b)[i]) {
return false;
}
}
return true;
}
function isEmptyString(string memory a) internal pure returns (bool) {
return (compareStrings(a, ""));
}
event CustodianBtcAddressForMerchantSet(address indexed merchant,
address indexed sender,
string btcDepositAddress);
function setCustodianBtcAddressForMerchant(
address merchant,
string memory btcAddress
)
external
onlyCustodian
returns (bool)
{
require((address)(merchant) != address(0), "invalid merchant address");
controller.requireMerchant(merchant);
require(!isEmptyString(btcAddress), "invalid btc address");
custodianBtcAddressForMerchant[merchant] = btcAddress;
emit CustodianBtcAddressForMerchantSet(merchant, msg.sender, btcAddress);
return true;
}
event BtcDepositAddressOfMerchantSet(address indexed merchant,
string btcDepositAddress);
function setMerchantBtcDepositAddress(string memory btcAddress)
external
onlyMerchant
returns (bool) {
require(!isEmptyString(btcAddress), "invalid btc address");
btcDepositAddressOfMerchant[msg.sender] = btcAddress;
emit BtcDepositAddressOfMerchantSet(msg.sender, btcAddress);
return true;
}
event NewMintRequest(
uint indexed seq,
address indexed requester,
string btcAddress,
string btcTxId,
uint blockNo,
bytes32 requestHash
);
function requestMint(
uint amount,
string memory btcTxId
)
external
onlyMerchant
returns (bool)
{
require(!isEmptyString(btcTxId), "invalid btcTxId");
require(!isEmptyString(custodianBtcAddressForMerchant[msg.sender]), "invalid btc deposit address");
uint seq = mintRequests.length;
uint blockNo = block.number;
Request memory request = Request({
requester : msg.sender,
amount : amount,
btcAddress : custodianBtcAddressForMerchant[msg.sender],
btcTxId : btcTxId,
seq : seq,
requestBlockNo : blockNo,
confirmedBlockNo : 0,
status : RequestStatus.PENDING
});
bytes32 requestHash = calcRequestHash(request);
mintRequestSeqMap[requestHash] = seq;
mintRequests.push(request);
emit NewMintRequest(seq, msg.sender, request.btcAddress, btcTxId, blockNo, requestHash);
return true;
}
function calcRequestHash(Request memory request) internal pure returns (bytes32) {
return keccak256(abi.encode(
request.requester,
request.btcAddress,
request.btcTxId,
request.seq,
request.requestBlockNo
));
}
event MintRequestCancel(uint indexed seq, address indexed requester, bytes32 requestHash);
function getPendingMintRequest(bytes32 _requestHash) view private returns (Request memory) {
uint seq = mintRequestSeqMap[_requestHash];
require(mintRequests.length > seq, "invalid seq");
require(seq > 0, "invalid requestHash");
Request memory request = mintRequests[seq];
require(request.status == RequestStatus.PENDING, "status not pending.");
require(_requestHash == calcRequestHash(request), "invalid hash");
return request;
}
function getPendingMintRequestV(bytes32 _requestHash) override view public returns (
uint requestSeq,
address requester,
uint amount,
string memory btcAddress,
string memory btcTxId,
uint requestBlockNo,
uint confirmedBlockNo,
string memory status) {
Request memory request = getPendingMintRequest(_requestHash);
requestSeq = request.seq;
requester = request.requester;
amount = request.amount;
btcAddress = request.btcAddress;
btcTxId = request.btcTxId;
requestBlockNo = request.requestBlockNo;
confirmedBlockNo = request.confirmedBlockNo;
status = getStatusString(request.status);
}
function cancelMintRequest(bytes32 requestHash) external onlyMerchant returns (bool) {
Request memory request = getPendingMintRequest(requestHash);
uint seq = request.seq;
require(msg.sender == request.requester, "cancel sender is different than pending request initiator");
mintRequests[seq].status = RequestStatus.CANCELED;
emit MintRequestCancel(request.seq, msg.sender, calcRequestHash(request));
return true;
}
event MintConfirmed(
uint indexed seq,
address indexed requester,
uint amount,
string btcDepositAddress,
string btcTxid,
uint blockNo,
bytes32 requestHash
);
function confirmMintRequest(bytes32 requestHash) external onlyCustodian returns (bool) {
uint blockNo = block.number;
Request memory request = getPendingMintRequest(requestHash);
require(blockNo > request.requestBlockNo, "confirmMintRequest failed");
require(blockNo - 20 >= request.requestBlockNo, "confirmMintRequest failed, wait for 20 blocks");
uint seq = request.seq;
mintRequests[seq].status = RequestStatus.APPROVED;
uint amount = mintRequests[seq].amount;
mintRequests[seq].confirmedBlockNo = blockNo;
require(controller.mint(request.requester, amount), "mint failed");
emit MintConfirmed(
request.seq,
request.requester,
amount,
request.btcAddress,
request.btcTxId,
blockNo,
calcRequestHash(request)
);
return true;
}
event MintRejected(
uint indexed seq,
address indexed requester,
uint amount,
string btcDepositAddress,
string btcTxid,
uint blockNo,
bytes32 requestHash
);
function rejectMintRequest(bytes32 requestHash) external onlyCustodian returns (bool) {
Request memory request = getPendingMintRequest(requestHash);
uint seq = request.seq;
mintRequests[seq].status = RequestStatus.REJECTED;
uint blockNo = block.number;
mintRequests[seq].confirmedBlockNo = blockNo;
emit MintRejected(
request.seq,
request.requester,
request.amount,
request.btcAddress,
request.btcTxId,
blockNo,
calcRequestHash(request)
);
return true;
}
event Burned(
uint indexed seq,
address indexed requester,
uint amount,
string btcAddress,
uint blockNo,
bytes32 requestHash
);
function burn(uint amount) external onlyMerchant returns (bool) {
string memory btcDepositAddress = btcDepositAddressOfMerchant[msg.sender];
require(!isEmptyString(btcDepositAddress), "merchant btc deposit address was not set");
uint seq = burnRequests.length;
uint blockNo = block.number;
Request memory request = Request({
requester : msg.sender,
amount : amount,
btcAddress : btcDepositAddress,
btcTxId : "",
seq : seq,
requestBlockNo : blockNo,
confirmedBlockNo : 0, //由确认阶段回填
status : RequestStatus.PENDING
});
bytes32 requestHash = calcRequestHash(request);
burnRequestSeqMap[requestHash] = seq;
burnRequests.push(request);
require(controller.getMToken().transferFrom(msg.sender, (address)(controller), amount), "trasnfer tokens to burn failed");
require(controller.burn(amount), "burn failed");
emit Burned(seq, msg.sender, amount, btcDepositAddress, blockNo, requestHash);
return true;
}
event BurnConfirmed(
uint indexed seq,
address indexed requester,
uint amount,
string btcAddress,
string btcTxId,
uint blockNo
);
function confirmBurnRequest(bytes32 requestHash, string memory btcTxId) external onlyCustodian returns (bool) {
uint seq = burnRequestSeqMap[requestHash];
require(burnRequests.length > seq, "invalid seq");
require(seq > 0, "invalid requestHash");
Request memory request = burnRequests[seq];
require(requestHash == calcRequestHash(request), "invalid requestHash");
require(request.status == RequestStatus.PENDING, "status not pending.");
burnRequests[seq].btcTxId = btcTxId;
burnRequests[seq].status = RequestStatus.APPROVED;
uint blockNo = block.number;
burnRequests[seq].confirmedBlockNo = blockNo;
request.btcTxId = btcTxId;
burnRequestSeqMap[calcRequestHash(request)] = seq;
emit BurnConfirmed(
request.seq,
request.requester,
request.amount,
request.btcAddress,
btcTxId,
blockNo
);
return true;
}
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental SMTChecker;
//pragma experimental ABIEncoderV2;
import "Ownable.sol";
import "MTokenControllerIf.sol";
/// @title MintFactoryIfView
abstract contract MintFactoryIfView {
MTokenControllerIf public controller;
mapping(address => string) public custodianBtcAddressForMerchant;
mapping(address => string) public btcDepositAddressOfMerchant;
enum RequestStatus {PENDING, CANCELED, APPROVED, REJECTED}
struct Request {
address requester;
uint amount;
string btcAddress;
string btcTxId;
uint seq;
uint requestBlockNo;
uint confirmedBlockNo;
RequestStatus status;
}
mapping(bytes32 => uint) public mintRequestSeqMap;
mapping(bytes32 => uint) public burnRequestSeqMap;
Request[] public mintRequests;
Request[] public burnRequests;
function getMintRequest(uint seq)
external
view
virtual
returns (
uint requestSeq,
address requester,
uint amount,
string memory btcAddress,
string memory btcTxId,
uint requestBlockNo,
uint confirmedBlockNo,
string memory status,
bytes32 requestHash
);
function getMintRequestsLength() virtual external view returns (uint length);
function getBurnRequest(uint seq)
external
view
virtual
returns (
uint requestSeq,
address requester,
uint amount,
string memory btcAddress,
string memory btcTxId,
uint requestBlockNo,
uint confirmedBlockNo,
string memory status,
bytes32 requestHash
);
function getBurnRequestsLength() virtual external view returns (uint length);
function getPendingMintRequestV(bytes32 _requestHash)
virtual
view public returns (
uint requestSeq,
address requester,
uint amount,
string memory btcAddress,
string memory btcTxId,
uint requestBlockNo,
uint confirmedBlockNo,
string memory status);
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental SMTChecker;
import "MemberMgrIf.sol";
import "ERC20If.sol";
/// @title ERC20ControllerViewIf
abstract contract ERC20ControllerViewIf {
function blocked(address _who) virtual public view returns (bool);
function paused() virtual public view returns (bool);
}
/// @title MTokenControllerIf
abstract contract MTokenControllerIf is MemberMgrIf, ERC20ControllerViewIf {
function mint(address to, uint amount) virtual external returns (bool);
function burn(uint value) virtual external returns (bool);
function getMToken() virtual external returns (ERC20If);
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental SMTChecker;
import "OwnableIf.sol";
// File: openzeppelin-solidity/contracts/ownership/Ownable.sol
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable is OwnableIf {
address public owner;
function _owner() view override public returns (address){
return owner;
}
// event OwnershipRenounced(address indexed previousOwner);
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor() {
owner = msg.sender;
}
// /**
// * @dev Throws if called by any account other than the owner.
// */
// modifier onlyOwner() {
// require(msg.sender == owner);
// _;
// }
/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
// function renounceOwnership() public onlyOwner {
// emit OwnershipRenounced(owner);
// owner = address(0);
// }
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param _newOwner The address to transfer ownership to.
*/
function transferOwnership(address _newOwner) virtual public onlyOwner {
_transferOwnership(_newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param _newOwner The address to transfer ownership to.
*/
function _transferOwnership(address _newOwner) internal {
require(_newOwner != address(0), "invalid _newOwner");
emit OwnershipTransferred(owner, _newOwner);
owner = _newOwner;
}
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental SMTChecker;
// File: openzeppelin-solidity/contracts/ownership/Ownable.sol
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
abstract contract OwnableIf {
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == _owner(), "not owner......");
_;
}
function _owner() view virtual public returns (address);
}