Solidity

建構子不會在部署時觸發

  • August 29, 2021

我有一個我不明白的問題。

我寫了一個代幣合約,將用作收益農業合約的獎勵。問題是,當我使用自己的編譯 + 部署腳本部署它時,建構子不會觸發,建構子根本不會被觸發。

我試圖在 remix 上編譯 + 部署它,建構子在 javascript VM 上被觸發並註入 web3。

這是我的編譯腳本:

function compileContract(arrayContractPath) {
   const contractPath = path.resolve(__dirname, ...arrayContractPath);
   
   const contractSourceCode = fs.readFileSync(contractPath, "utf8");
   
   fs.ensureDirSync(buildPath);

   var input = {
       language: "Solidity",
       sources: {
           Contract: {
               content: contractSourceCode
           }
       },
       settings: {
           optimizer: {
               enabled: true
           },
           outputSelection: {
               "*": {
                   "*": [ "*" ]
               }
           }
       }
   };

   const intermediateFoldersOfCurrentContract = arrayContractPath.slice(1, -1);

   function findImports(path) {
       if(path[0] === "@") { // directly into node_ module
           const sourceCodeToImport = fs.readFileSync(`../../../node_modules/${path}`);
           return { contents: `${sourceCodeToImport}` };
       } 
       if (arrayContractPath.length === 2) { // array contract path is "./" + contract.sol, i.e the compield contract is in the same folder as compile.js => import paths have the same starting point, no need to change them
           const sourceCodeToImport = fs.readFileSync(`./${path}`);
           return { contents: `${sourceCodeToImport}` };
       }
       if(!path.includes("/")) { // === contract to import is in the same folder as the contract we are compiling i.e the import path from the contract fiel doesn't include "/"
           const sourceCodeToImport = fs.readFileSync(`./${intermediateFoldersOfCurrentContract.join("/")}/${path}`);
           return { contents: `${sourceCodeToImport}` };
       }
       else { // if neither of these, contract must be (in my case) accessible from the compile.js, i.e no need to change the path
           const sourceCodeToImport = fs.readFileSync(`./${path}`);
           return { contents: `${sourceCodeToImport}` }
       }
     }

   let output = JSON.parse(solc.compile(JSON.stringify(input), { import: findImports }));

   for(let contractName in output.contracts.Contract) {
       fs.outputJsonSync(
           path.resolve(buildPath, `${contractName}.json`),
           output.contracts.Contract[contractName]
       );
   }    
}

這是我的部署腳本:

"use-strict";

const HDWalletProvider = require("truffle-hdwallet-provider");
const Web3 = require("web3");

const compiledToken= require("./build/Token.json");

const provider = new HDWalletProvider("PRIVATE_KEY", "NODE_API");

const web3 = new Web3(provider);

const deploy = async () => {
   accounts = await web3.eth.getAccounts();

   console.log(`Attempting to deploy from ${accounts[0]}`);

   const deployedToken = await new web3.eth.Contract(compiledToken.abi).deploy({ data: compiledToken.evm.bytecode.object, arguments: [web3.utils.toWei('100000000000','ether')] }).send({gas: "5000000", from: accounts[0]});


   console.log("token deployed to", deployedToken.options.address);
}

deploy();

短代幣合約:

pragma solidity ^0.6.6;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Tokenis ERC20 {
   address public admin;
   address public liquidator;
   
   constructor() ERC20("Token", "TKN", 10000000000000000000000000000) public {
       admin = msg.sender;
   }

   function setLiquidator(address _liquidator) external {
       require(msg.sender == admin, "only admin");
       liquidator = _liquidator;
   } 

   function mint(address to, uint amount) external {
       require(msg.sender == liquidator, "only liquidator");
       _mint(to, amount);
   }
   
}

完整的代幣合約重現:

pragma solidity ^0.6.6;

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
   function _msgSender() internal view virtual returns (address payable) {
       return msg.sender;
   }

   function _msgData() internal view virtual returns (bytes memory) {
       this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
       return msg.data;
   }
}


// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
   /**
    * @dev Returns the amount of tokens in existence.
    */
   function totalSupply() external view returns (uint256);

   /**
    * @dev Returns the amount of tokens owned by `account`.
    */
   function balanceOf(address account) external view returns (uint256);

   /**
    * @dev Moves `amount` tokens from the caller's account to `recipient`.
    *
    * Returns a boolean value indicating whether the operation succeeded.
    *
    * Emits a {Transfer} event.
    */
   function transfer(address recipient, uint256 amount) external returns (bool);

   /**
    * @dev Returns the remaining number of tokens that `spender` will be
    * allowed to spend on behalf of `owner` through {transferFrom}. This is
    * zero by default.
    *
    * This value changes when {approve} or {transferFrom} are called.
    */
   function allowance(address owner, address spender) external view returns (uint256);

   /**
    * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
    *
    * Returns a boolean value indicating whether the operation succeeded.
    *
    * IMPORTANT: Beware that changing an allowance with this method brings the risk
    * that someone may use both the old and the new allowance by unfortunate
    * transaction ordering. One possible solution to mitigate this race
    * condition is to first reduce the spender's allowance to 0 and set the
    * desired value afterwards:
    * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
    *
    * Emits an {Approval} event.
    */
   function approve(address spender, uint256 amount) external returns (bool);

   /**
    * @dev Moves `amount` tokens from `sender` to `recipient` using the
    * allowance mechanism. `amount` is then deducted from the caller's
    * allowance.
    *
    * Returns a boolean value indicating whether the operation succeeded.
    *
    * Emits a {Transfer} event.
    */
   function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

   /**
    * @dev Emitted when `value` tokens are moved from one account (`from`) to
    * another (`to`).
    *
    * Note that `value` may be zero.
    */
   event Transfer(address indexed from, address indexed to, uint256 value);

   /**
    * @dev Emitted when the allowance of a `spender` for an `owner` is set by
    * a call to {approve}. `value` is the new allowance.
    */
   event Approval(address indexed owner, address indexed spender, uint256 value);
}
   

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
   /**
    * @dev Returns the addition of two unsigned integers, reverting on
    * overflow.
    *
    * Counterpart to Solidity's `+` operator.
    *
    * Requirements:
    *
    * - Addition cannot overflow.
    */
   function add(uint256 a, uint256 b) internal pure returns (uint256) {
       uint256 c = a + b;
       require(c >= a, "SafeMath: addition overflow");

       return c;
   }

   /**
    * @dev Returns the subtraction of two unsigned integers, reverting on
    * overflow (when the result is negative).
    *
    * Counterpart to Solidity's `-` operator.
    *
    * Requirements:
    *
    * - Subtraction cannot overflow.
    */
   function sub(uint256 a, uint256 b) internal pure returns (uint256) {
       return sub(a, b, "SafeMath: subtraction overflow");
   }

   /**
    * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
    * overflow (when the result is negative).
    *
    * Counterpart to Solidity's `-` operator.
    *
    * Requirements:
    *
    * - Subtraction cannot overflow.
    */
   function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
       require(b <= a, errorMessage);
       uint256 c = a - b;

       return c;
   }

   /**
    * @dev Returns the multiplication of two unsigned integers, reverting on
    * overflow.
    *
    * Counterpart to Solidity's `*` operator.
    *
    * Requirements:
    *
    * - Multiplication cannot overflow.
    */
   function mul(uint256 a, uint256 b) internal pure returns (uint256) {
       // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
       // benefit is lost if 'b' is also tested.
       // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
       if (a == 0) {
           return 0;
       }

       uint256 c = a * b;
       require(c / a == b, "SafeMath: multiplication overflow");

       return c;
   }

   /**
    * @dev Returns the integer division of two unsigned integers. Reverts on
    * division by zero. The result is rounded towards zero.
    *
    * Counterpart to Solidity's `/` operator. Note: this function uses a
    * `revert` opcode (which leaves remaining gas untouched) while Solidity
    * uses an invalid opcode to revert (consuming all remaining gas).
    *
    * Requirements:
    *
    * - The divisor cannot be zero.
    */
   function div(uint256 a, uint256 b) internal pure returns (uint256) {
       return div(a, b, "SafeMath: division by zero");
   }

   /**
    * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
    * division by zero. The result is rounded towards zero.
    *
    * Counterpart to Solidity's `/` operator. Note: this function uses a
    * `revert` opcode (which leaves remaining gas untouched) while Solidity
    * uses an invalid opcode to revert (consuming all remaining gas).
    *
    * Requirements:
    *
    * - The divisor cannot be zero.
    */
   function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
       require(b > 0, errorMessage);
       uint256 c = a / b;
       // assert(a == b * c + a % b); // There is no case in which this doesn't hold

       return c;
   }

   /**
    * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
    * Reverts when dividing by zero.
    *
    * Counterpart to Solidity's `%` operator. This function uses a `revert`
    * opcode (which leaves remaining gas untouched) while Solidity uses an
    * invalid opcode to revert (consuming all remaining gas).
    *
    * Requirements:
    *
    * - The divisor cannot be zero.
    */
   function mod(uint256 a, uint256 b) internal pure returns (uint256) {
       return mod(a, b, "SafeMath: modulo by zero");
   }

   /**
    * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
    * Reverts with custom message when dividing by zero.
    *
    * Counterpart to Solidity's `%` operator. This function uses a `revert`
    * opcode (which leaves remaining gas untouched) while Solidity uses an
    * invalid opcode to revert (consuming all remaining gas).
    *
    * Requirements:
    *
    * - The divisor cannot be zero.
    */
   function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
       require(b != 0, errorMessage);
       return a % b;
   }
}


// SPDX-License-Identifier: MIT

pragma solidity ^0.6.2;

/**
* @dev Collection of functions related to the address type
*/
library Address {
   /**
    * @dev Returns true if `account` is a contract.
    *
    * [IMPORTANT]
    * ====
    * It is unsafe to assume that an address for which this function returns
    * false is an externally-owned account (EOA) and not a contract.
    *
    * Among others, `isContract` will return false for the following
    * types of addresses:
    *
    *  - an externally-owned account
    *  - a contract in construction
    *  - an address where a contract will be created
    *  - an address where a contract lived, but was destroyed
    * ====
    */
   function isContract(address account) internal view returns (bool) {
       // This method relies in extcodesize, which returns 0 for contracts in
       // construction, since the code is only stored at the end of the
       // constructor execution.

       uint256 size;
       // solhint-disable-next-line no-inline-assembly
       assembly { size := extcodesize(account) }
       return size > 0;
   }

   /**
    * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
    * `recipient`, forwarding all available gas and reverting on errors.
    *
    * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
    * of certain opcodes, possibly making contracts go over the 2300 gas limit
    * imposed by `transfer`, making them unable to receive funds via
    * `transfer`. {sendValue} removes this limitation.
    *
    * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
    *
    * IMPORTANT: because control is transferred to `recipient`, care must be
    * taken to not create reentrancy vulnerabilities. Consider using
    * {ReentrancyGuard} or the
    * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
    */
   function sendValue(address payable recipient, uint256 amount) internal {
       require(address(this).balance >= amount, "Address: insufficient balance");

       // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
       (bool success, ) = recipient.call{ value: amount }("");
       require(success, "Address: unable to send value, recipient may have reverted");
   }

   /**
    * @dev Performs a Solidity function call using a low level `call`. A
    * plain`call` is an unsafe replacement for a function call: use this
    * function instead.
    *
    * If `target` reverts with a revert reason, it is bubbled up by this
    * function (like regular Solidity function calls).
    *
    * Returns the raw returned data. To convert to the expected return value,
    * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
    *
    * Requirements:
    *
    * - `target` must be a contract.
    * - calling `target` with `data` must not revert.
    *
    * _Available since v3.1._
    */
   function functionCall(address target, bytes memory data) internal returns (bytes memory) {
     return functionCall(target, data, "Address: low-level call failed");
   }

   /**
    * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
    * `errorMessage` as a fallback revert reason when `target` reverts.
    *
    * _Available since v3.1._
    */
   function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
       return _functionCallWithValue(target, data, 0, errorMessage);
   }

   /**
    * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
    * but also transferring `value` wei to `target`.
    *
    * Requirements:
    *
    * - the calling contract must have an ETH balance of at least `value`.
    * - the called Solidity function must be `payable`.
    *
    * _Available since v3.1._
    */
   function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
       return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
   }

   /**
    * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
    * with `errorMessage` as a fallback revert reason when `target` reverts.
    *
    * _Available since v3.1._
    */
   function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
       require(address(this).balance >= value, "Address: insufficient balance for call");
       return _functionCallWithValue(target, data, value, errorMessage);
   }

   function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
       require(isContract(target), "Address: call to non-contract");

       // solhint-disable-next-line avoid-low-level-calls
       (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
       if (success) {
           return returndata;
       } else {
           // Look for revert reason and bubble it up if present
           if (returndata.length > 0) {
               // The easiest way to bubble the revert reason is using memory via assembly

               // solhint-disable-next-line no-inline-assembly
               assembly {
                   let returndata_size := mload(returndata)
                   revert(add(32, returndata), returndata_size)
               }
           } else {
               revert(errorMessage);
           }
       }
   }
}


/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin guidelines: functions revert instead
* of returning `false` on failure. This behavior is nonetheless conventional
* and does not conflict with the expectations of ERC20 applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20 {
   using SafeMath for uint256;
   using Address for address;

   mapping (address => uint256) private _balances;

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

   uint256 private _totalSupply;

   string private _name;
   string private _symbol;
   uint8 private _decimals;

   /**
    * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
    * a default value of 18.
    *
    * To select a different value for {decimals}, use {_setupDecimals}.
    *
    * All three of these values are immutable: they can only be set once during
    * construction.
    */
   constructor (string memory name, string memory symbol, uint256 totalSupply) public {
       _name = name;
       _symbol = symbol;
       _totalSupply = totalSupply;
       _decimals = 18;
   }

   /**
    * @dev Returns the name of the token.
    */
   function name() public view returns (string memory) {
       return _name;
   }

   /**
    * @dev Returns the symbol of the token, usually a shorter version of the
    * name.
    */
   function symbol() public view returns (string memory) {
       return _symbol;
   }

   /**
    * @dev Returns the number of decimals used to get its user representation.
    * For example, if `decimals` equals `2`, a balance of `505` tokens should
    * be displayed to a user as `5,05` (`505 / 10 ** 2`).
    *
    * Tokens usually opt for a value of 18, imitating the relationship between
    * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
    * called.
    *
    * NOTE: This information is only used for _display_ purposes: it in
    * no way affects any of the arithmetic of the contract, including
    * {IERC20-balanceOf} and {IERC20-transfer}.
    */
   function decimals() public view returns (uint8) {
       return _decimals;
   }

   /**
    * @dev See {IERC20-totalSupply}.
    */
   function totalSupply() public view override returns (uint256) {
       return _totalSupply;
   }

   /**
    * @dev See {IERC20-balanceOf}.
    */
   function balanceOf(address account) public view override returns (uint256) {
       return _balances[account];
   }

   /**
    * @dev See {IERC20-transfer}.
    *
    * Requirements:
    *
    * - `recipient` cannot be the zero address.
    * - the caller must have a balance of at least `amount`.
    */
   function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
       _transfer(_msgSender(), recipient, amount);
       return true;
   }

   /**
    * @dev See {IERC20-allowance}.
    */
   function allowance(address owner, address spender) public view virtual override returns (uint256) {
       return _allowances[owner][spender];
   }

   /**
    * @dev See {IERC20-approve}.
    *
    * Requirements:
    *
    * - `spender` cannot be the zero address.
    */
   function approve(address spender, uint256 amount) public virtual override returns (bool) {
       _approve(_msgSender(), spender, amount);
       return true;
   }

   /**
    * @dev See {IERC20-transferFrom}.
    *
    * Emits an {Approval} event indicating the updated allowance. This is not
    * required by the EIP. See the note at the beginning of {ERC20};
    *
    * Requirements:
    * - `sender` and `recipient` cannot be the zero address.
    * - `sender` must have a balance of at least `amount`.
    * - the caller must have allowance for ``sender``'s tokens of at least
    * `amount`.
    */
   function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
       _transfer(sender, recipient, amount);
       _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
       return true;
   }

   /**
    * @dev Atomically increases the allowance granted to `spender` by the caller.
    *
    * This is an alternative to {approve} that can be used as a mitigation for
    * problems described in {IERC20-approve}.
    *
    * Emits an {Approval} event indicating the updated allowance.
    *
    * Requirements:
    *
    * - `spender` cannot be the zero address.
    */
   function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
       _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
       return true;
   }

   function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
       _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
       return true;
   }

   function _transfer(address sender, address recipient, uint256 amount) internal virtual {
       require(sender != address(0), "ERC20: transfer from the zero address");
       require(recipient != address(0), "ERC20: transfer to the zero address");

       _beforeTokenTransfer(sender, recipient, amount);

       _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
       _balances[recipient] = _balances[recipient].add(amount);
       emit Transfer(sender, recipient, amount);
   }

   function _mint(address account, uint256 amount) internal virtual {
       require(account != address(0), "ERC20: mint to the zero address");

       _beforeTokenTransfer(address(0), account, amount);

       _totalSupply = _totalSupply.add(amount);
       _balances[account] = _balances[account].add(amount);
       emit Transfer(address(0), account, amount);
   }

   function _burn(address account, uint256 amount) internal virtual {
       require(account != address(0), "ERC20: burn from the zero address");

       _beforeTokenTransfer(account, address(0), amount);

       _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
       _totalSupply = _totalSupply.sub(amount);
       emit Transfer(account, address(0), amount);
   }

   function _approve(address owner, address spender, uint256 amount) internal virtual {
       require(owner != address(0), "ERC20: approve from the zero address");
       require(spender != address(0), "ERC20: approve to the zero address");

       _allowances[owner][spender] = amount;
       emit Approval(owner, spender, amount);
   }

   function _setupDecimals(uint8 decimals_) internal {
       _decimals = decimals_;
   }

   function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}


contract Tokenis ERC20 {
   address public admin;
   address public liquidator;
   
   constructor() ERC20("Token", "TKN", 10000000000000000000000000000) public {
       admin = msg.sender;
   }

   function setLiquidator(address _liquidator) external {
       require(msg.sender == admin, "only admin");
       liquidator = _liquidator;
   } 

   function mint(address to, uint amount) external {
       require(msg.sender == liquidator, "only liquidator");
       _mint(to, amount);
   }
   
}
}

如果您想與在部署時未觸發建構子的合約進行互動,您可以使用上面的合約並與 rinkeby 上的此地址進行互動:“0x4C7C45D4F7B09d09178379E87Ed7366ccC20f208”,然後您會看到每個變數,如 totalSupply、admin、小數等…都設置為預設值。

我不明白問題出在哪裡。有任何想法嗎 ?

先感謝您

  1. 僅保留一個 SPDX 許可證並刪除其他許可證
  2. 您的代幣合約中有拼寫錯誤和多餘的括號。用這個替換它 -
contract Token is ERC20 {

   address public admin;
    address public liquidator;

    constructor() ERC20("Token", "TKN", 10000000000000000000000000000) public {
        admin = msg.sender;
    }

    function setLiquidator(address _liquidator) external {
        require(msg.sender == admin, "only admin");
        liquidator = _liquidator;
    } 

    function mint(address to, uint amount) external {
        require(msg.sender == liquidator, "only liquidator");
        _mint(to, amount);
    }

}

引用自:https://ethereum.stackexchange.com/questions/107962