Transaction Hash:
Block:
13944398 at Jan-05-2022 08:37:24 AM +UTC
Transaction Fee:
0.00881366003357871 ETH
$16.54
Gas Used:
92,615 Gas / 95.164498554 Gwei
Emitted Events:
264 |
ADXToken.Transfer( from=0x0000000000000000000000000000000000000000, to=[Receiver] StakingPool, amount=988077118214104312800 )
|
265 |
StakingPool.Transfer( from=0x0000000000000000000000000000000000000000, to=[Sender] 0x7edbc2e9375be041ee61f208f47aa1a92f377ce5, amount=1980019091137917132204 )
|
266 |
ADXToken.Transfer( from=[Sender] 0x7edbc2e9375be041ee61f208f47aa1a92f377ce5, to=[Receiver] StakingPool, amount=3199588487800000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x1aD91ee0...dA6B45836
Miner
| (Hiveon Pool) | 9,035.648605339703337594 Eth | 9,035.648684128193779969 Eth | 0.000078788490442375 | |
0x7EDbc2E9...92f377Ce5 |
0.498248100362999263 Eth
Nonce: 43
|
0.489434440329420553 Eth
Nonce: 44
| 0.00881366003357871 | ||
0x9D47f1c6...68BC9094e | |||||
0xADE00C28...8cD12B7c3 | |||||
0xB6456b57...a0813491a |
Execution Trace
StakingPool.enter( amount=3199588487800000000000 )

-
ADXToken.STATICCALL( )
ADXSupplyController.mintIncentive( addr=0xB6456b57f03352bE48Bf101B46c1752a0813491a )
-
ADXToken.CALL( )
-
ADXToken.mint( owner=0xB6456b57f03352bE48Bf101B46c1752a0813491a, amount=988077118214104312800 )
-
-
ADXToken.balanceOf( owner=0xB6456b57f03352bE48Bf101B46c1752a0813491a ) => ( balance=45238428764342834570877766 )
-
ADXToken.transferFrom( from=0x7EDbc2E9375BE041EE61f208F47Aa1A92f377Ce5, to=0xB6456b57f03352bE48Bf101B46c1752a0813491a, amount=3199588487800000000000 ) => ( success=True )
enter[StakingPool (ln:249)]
innerEnter[StakingPool (ln:250)]
mintIncentive[StakingPool (ln:234)]
supplyController[StakingPool (ln:234)]
balanceOf[StakingPool (ln:236)]
innerMint[StakingPool (ln:240)]
Transfer[StakingPool (ln:116)]
innerMint[StakingPool (ln:243)]
Transfer[StakingPool (ln:116)]
transferFrom[StakingPool (ln:245)]
File 1 of 3: StakingPool
File 2 of 3: ADXToken
File 3 of 3: ADXSupplyController
pragma solidity 0.8.1; interface ISupplyController { function mintIncentive(address addr) external; function mintableIncentive(address addr) external view returns (uint); function mint(address token, address owner, uint amount) external; function changeSupplyController(address newSupplyController) external; } interface IADXToken { function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); function approve(address spender, uint256 amount) external returns (bool); function balanceOf(address spender) external view returns (uint); function allowance(address owner, address spender) external view returns (uint); function totalSupply() external returns (uint); function supplyController() external view returns (ISupplyController); function changeSupplyController(address newSupplyController) external; function mint(address owner, uint amount) external; } interface IERCDecimals { function decimals() external view returns (uint); } interface IChainlink { // AUDIT: ensure this API is not deprecated function latestAnswer() external view returns (uint); } // Full interface here: https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/interfaces/IUniswapV2Router01.sol interface IUniswapSimple { function WETH() external pure returns (address); function swapTokensForExactTokens( uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline ) external returns (uint[] memory amounts); } contract StakingPool { // ERC20 stuff // Constants string public constant name = "AdEx Staking Token"; uint8 public constant decimals = 18; string public constant symbol = "ADX-STAKING"; // Mutable variables uint public totalSupply; mapping(address => uint) private balances; mapping(address => mapping(address => uint)) private allowed; // EIP 2612 bytes32 public DOMAIN_SEPARATOR; // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; mapping(address => uint) public nonces; // ERC20 events event Approval(address indexed owner, address indexed spender, uint amount); event Transfer(address indexed from, address indexed to, uint amount); // ERC20 methods function balanceOf(address owner) external view returns (uint balance) { return balances[owner]; } function transfer(address to, uint amount) external returns (bool success) { require(to != address(this), "BAD_ADDRESS"); balances[msg.sender] = balances[msg.sender] - amount; balances[to] = balances[to] + amount; emit Transfer(msg.sender, to, amount); return true; } function transferFrom(address from, address to, uint amount) external returns (bool success) { balances[from] = balances[from] - amount; allowed[from][msg.sender] = allowed[from][msg.sender] - amount; balances[to] = balances[to] + amount; emit Transfer(from, to, amount); return true; } function approve(address spender, uint amount) external returns (bool success) { allowed[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function allowance(address owner, address spender) external view returns (uint remaining) { return allowed[owner][spender]; } // EIP 2612 function permit(address owner, address spender, uint amount, uint deadline, uint8 v, bytes32 r, bytes32 s) external { require(deadline >= block.timestamp, "DEADLINE_EXPIRED"); bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, amount, nonces[owner]++, deadline)) )); address recoveredAddress = ecrecover(digest, v, r, s); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNATURE"); allowed[owner][spender] = amount; emit Approval(owner, spender, amount); } // Inner function innerMint(address owner, uint amount) internal { totalSupply = totalSupply + amount; balances[owner] = balances[owner] + amount; // Because of https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transfer-1 emit Transfer(address(0), owner, amount); } function innerBurn(address owner, uint amount) internal { totalSupply = totalSupply - amount; balances[owner] = balances[owner] - amount; emit Transfer(owner, address(0), amount); } // Pool functionality uint public timeToUnbond = 20 days; uint public rageReceivedPromilles = 700; IUniswapSimple public uniswap; // = IUniswapSimple(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); IChainlink public ADXUSDOracle; // = IChainlink(0x231e764B44b2C1b7Ca171fa8021A24ed520Cde10); IADXToken public immutable ADXToken; address public guardian; address public validator; address public governance; // claim token whitelist: normally claim tokens are stablecoins // eg Tether (0xdAC17F958D2ee523a2206206994597C13D831ec7) mapping (address => bool) public whitelistedClaimTokens; // Commitment ID against the max amount of tokens it will pay out mapping (bytes32 => uint) public commitments; // How many of a user's shares are locked mapping (address => uint) public lockedShares; // Unbonding commitment from a staker struct UnbondCommitment { address owner; uint shares; uint unlocksAt; } // claims/penalizations limits uint public maxDailyPenaltiesPromilles; uint public limitLastReset; uint public limitRemaining; // Staking pool events // LogLeave/LogWithdraw must begin with the UnbondCommitment struct event LogLeave(address indexed owner, uint shares, uint unlocksAt, uint maxTokens); event LogWithdraw(address indexed owner, uint shares, uint unlocksAt, uint maxTokens, uint receivedTokens); event LogRageLeave(address indexed owner, uint shares, uint maxTokens, uint receivedTokens); event LogNewGuardian(address newGuardian); event LogClaim(address tokenAddr, address to, uint amountInUSD, uint burnedValidatorShares, uint usedADX, uint totalADX, uint totalShares); event LogPenalize(uint burnedADX); constructor(IADXToken token, IUniswapSimple uni, IChainlink oracle, address guardianAddr, address validatorStakingWallet, address governanceAddr, address claimToken) { ADXToken = token; uniswap = uni; ADXUSDOracle = oracle; guardian = guardianAddr; validator = validatorStakingWallet; governance = governanceAddr; whitelistedClaimTokens[claimToken] = true; // EIP 2612 uint chainId; assembly { chainId := chainid() } DOMAIN_SEPARATOR = keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256(bytes("1")), chainId, address(this) ) ); } // Governance functions function setGovernance(address addr) external { require(governance == msg.sender, "NOT_GOVERNANCE"); governance = addr; } function setDailyPenaltyMax(uint max) external { require(governance == msg.sender, "NOT_GOVERNANCE"); require(max <= 200, "DAILY_PENALTY_TOO_LARGE"); maxDailyPenaltiesPromilles = max; resetLimits(); } function setRageReceived(uint rageReceived) external { require(governance == msg.sender, "NOT_GOVERNANCE"); // AUDIT: should there be a minimum here? require(rageReceived <= 1000, "TOO_LARGE"); rageReceivedPromilles = rageReceived; } function setTimeToUnbond(uint time) external { require(governance == msg.sender, "NOT_GOVERNANCE"); require(time >= 1 days && time <= 30 days, "BOUNDS"); timeToUnbond = time; } function setGuardian(address newGuardian) external { require(governance == msg.sender, "NOT_GOVERNANCE"); guardian = newGuardian; emit LogNewGuardian(newGuardian); } function setWhitelistedClaimToken(address token, bool whitelisted) external { require(governance == msg.sender, "NOT_GOVERNANCE"); whitelistedClaimTokens[token] = whitelisted; } // Pool stuff function shareValue() external view returns (uint) { if (totalSupply == 0) return 0; return ((ADXToken.balanceOf(address(this)) + ADXToken.supplyController().mintableIncentive(address(this))) * 1e18) / totalSupply; } function innerEnter(address recipient, uint amount) internal { // Please note that minting has to be in the beginning so that we take it into account // when using ADXToken.balanceOf() // Minting makes an external call but it"s to a trusted contract (ADXToken) ADXToken.supplyController().mintIncentive(address(this)); uint totalADX = ADXToken.balanceOf(address(this)); // The totalADX == 0 check here should be redudnant; the only way to get totalSupply to a nonzero val is by adding ADX if (totalSupply == 0 || totalADX == 0) { innerMint(recipient, amount); } else { uint256 newShares = (amount * totalSupply) / totalADX; innerMint(recipient, newShares); } require(ADXToken.transferFrom(msg.sender, address(this), amount)); // no events, as innerMint already emits enough to know the shares amount and price } function enter(uint amount) external { innerEnter(msg.sender, amount); } function enterTo(address recipient, uint amount) external { innerEnter(recipient, amount); } function unbondingCommitmentWorth(address owner, uint shares, uint unlocksAt) external view returns (uint) { if (totalSupply == 0) return 0; bytes32 commitmentId = keccak256(abi.encode(UnbondCommitment({ owner: owner, shares: shares, unlocksAt: unlocksAt }))); uint maxTokens = commitments[commitmentId]; uint totalADX = ADXToken.balanceOf(address(this)); uint currentTokens = (shares * totalADX) / totalSupply; return currentTokens > maxTokens ? maxTokens : currentTokens; } function leave(uint shares, bool skipMint) external { if (!skipMint) ADXToken.supplyController().mintIncentive(address(this)); require(shares <= balances[msg.sender] - lockedShares[msg.sender], "INSUFFICIENT_SHARES"); uint totalADX = ADXToken.balanceOf(address(this)); uint maxTokens = (shares * totalADX) / totalSupply; uint unlocksAt = block.timestamp + timeToUnbond; bytes32 commitmentId = keccak256(abi.encode(UnbondCommitment({ owner: msg.sender, shares: shares, unlocksAt: unlocksAt }))); require(commitments[commitmentId] == 0, "COMMITMENT_EXISTS"); commitments[commitmentId] = maxTokens; lockedShares[msg.sender] += shares; emit LogLeave(msg.sender, shares, unlocksAt, maxTokens); } function withdraw(uint shares, uint unlocksAt, bool skipMint) external { if (!skipMint) ADXToken.supplyController().mintIncentive(address(this)); require(block.timestamp > unlocksAt, "UNLOCK_TOO_EARLY"); bytes32 commitmentId = keccak256(abi.encode(UnbondCommitment({ owner: msg.sender, shares: shares, unlocksAt: unlocksAt }))); uint maxTokens = commitments[commitmentId]; require(maxTokens > 0, "NO_COMMITMENT"); uint totalADX = ADXToken.balanceOf(address(this)); uint currentTokens = (shares * totalADX) / totalSupply; uint receivedTokens = currentTokens > maxTokens ? maxTokens : currentTokens; commitments[commitmentId] = 0; lockedShares[msg.sender] -= shares; innerBurn(msg.sender, shares); require(ADXToken.transfer(msg.sender, receivedTokens)); emit LogWithdraw(msg.sender, shares, unlocksAt, maxTokens, receivedTokens); } function rageLeave(uint shares, bool skipMint) external { if (!skipMint) ADXToken.supplyController().mintIncentive(address(this)); uint totalADX = ADXToken.balanceOf(address(this)); uint adxAmount = (shares * totalADX) / totalSupply; uint receivedTokens = (adxAmount * rageReceivedPromilles) / 1000; innerBurn(msg.sender, shares); require(ADXToken.transfer(msg.sender, receivedTokens)); emit LogRageLeave(msg.sender, shares, adxAmount, receivedTokens); } // Insurance mechanism // In case something goes wrong, this can be used to recoup funds // As of V5, the idea is to use it to provide some interest (eg 10%) for late refunds, in case channels get stuck and have to wait through their challenge period function claim(address tokenOut, address to, uint amount) external { require(msg.sender == guardian, "NOT_GUARDIAN"); // start by resetting claim/penalty limits resetLimits(); // NOTE: minting is intentionally skipped here // This means that a validator may be punished a bit more when burning their shares, // but it guarantees that claim() always works uint totalADX = ADXToken.balanceOf(address(this)); // Note: whitelist of tokenOut tokens require(whitelistedClaimTokens[tokenOut], "TOKEN_NOT_WHITELISTED"); address[] memory path = new address[](3); path[0] = address(ADXToken); path[1] = uniswap.WETH(); path[2] = tokenOut; // You may think the Uniswap call enables reentrancy, but reentrancy is a problem only if the pattern is check-call-modify, not call-check-modify as is here // there"s no case in which we "double-spend" a value // Plus, ADX, USDT and uniswap are all trusted // Slippage protection; 5% slippage allowed uint price = ADXUSDOracle.latestAnswer(); // chainlink price is in 1e8 // for example, if the amount is in 1e6; // we need to convert from 1e6 to 1e18 (adx) but we divide by 1e8 (price); 18 - 6 + 8 ; verified this by calculating manually uint multiplier = 1.05e26 / (10 ** IERCDecimals(tokenOut).decimals()); uint adxAmountMax = (amount * multiplier) / price; require(adxAmountMax < totalADX, "INSUFFICIENT_ADX"); uint[] memory amounts = uniswap.swapTokensForExactTokens(amount, adxAmountMax, path, to, block.timestamp); // calculate the total ADX amount used in the swap uint adxAmountUsed = amounts[0]; // burn the validator shares so that they pay for it first, before dilluting other holders // calculate the worth in ADX of the validator"s shares uint sharesNeeded = (adxAmountUsed * totalSupply) / totalADX; uint toBurn = sharesNeeded < balances[validator] ? sharesNeeded : balances[validator]; if (toBurn > 0) innerBurn(validator, toBurn); // Technically redundant cause we"ll fail on the subtraction, but we"re doing this for better err msgs require(limitRemaining >= adxAmountUsed, "LIMITS"); limitRemaining -= adxAmountUsed; emit LogClaim(tokenOut, to, amount, toBurn, adxAmountUsed, totalADX, totalSupply); } function penalize(uint adxAmount) external { require(msg.sender == guardian, "NOT_GUARDIAN"); // AUDIT: we can do getLimitRemaining() instead of resetLimits() that returns the remaining limit resetLimits(); // Technically redundant cause we'll fail on the subtraction, but we're doing this for better err msgs require(limitRemaining >= adxAmount, "LIMITS"); limitRemaining -= adxAmount; require(ADXToken.transfer(address(0), adxAmount)); emit LogPenalize(adxAmount); } function resetLimits() internal { if (block.timestamp - limitLastReset > 24 hours) { limitLastReset = block.timestamp; limitRemaining = (ADXToken.balanceOf(address(this)) * maxDailyPenaltiesPromilles) / 1000; } } }
File 2 of 3: ADXToken
pragma solidity ^0.6.12; // SPDX-License-Identifier: agpl-3.0 library SafeMath { function mul(uint a, uint b) internal pure returns (uint) { uint c = a * b; require(a == 0 || c / a == b); return c; } function div(uint a, uint b) internal pure returns (uint) { require(b > 0); uint c = a / b; require(a == b * c + a % b); return c; } function sub(uint a, uint b) internal pure returns (uint) { require(b <= a); return a - b; } function add(uint a, uint b) internal pure returns (uint) { uint c = a + b; require(c >= a); return c; } function max64(uint64 a, uint64 b) internal pure returns (uint64) { return a >= b ? a : b; } function min64(uint64 a, uint64 b) internal pure returns (uint64) { return a < b ? a : b; } function max256(uint a, uint b) internal pure returns (uint) { return a >= b ? a : b; } function min256(uint a, uint b) internal pure returns (uint) { return a < b ? a : b; } } // NOTE: this interface lacks return values for transfer/transferFrom/approve on purpose, // as we use the SafeERC20 library to check the return value interface GeneralERC20 { function transfer(address to, uint256 amount) external; function transferFrom(address from, address to, uint256 amount) external; function approve(address spender, uint256 amount) external; function balanceOf(address spender) external view returns (uint); function allowance(address owner, address spender) external view returns (uint); } library SafeERC20 { function checkSuccess() private pure returns (bool) { uint256 returnValue = 0; assembly { // check number of bytes returned from last function call switch returndatasize() // no bytes returned: assume success case 0x0 { returnValue := 1 } // 32 bytes returned: check if non-zero case 0x20 { // copy 32 bytes into scratch space returndatacopy(0x0, 0x0, 0x20) // load those bytes into returnValue returnValue := mload(0x0) } // not sure what was returned: don't mark as success default { } } return returnValue != 0; } function transfer(address token, address to, uint256 amount) internal { GeneralERC20(token).transfer(to, amount); require(checkSuccess()); } function transferFrom(address token, address from, address to, uint256 amount) internal { GeneralERC20(token).transferFrom(from, to, amount); require(checkSuccess()); } function approve(address token, address spender, uint256 amount) internal { GeneralERC20(token).approve(spender, amount); require(checkSuccess()); } } contract ADXToken { using SafeMath for uint; // Constants string public constant name = "AdEx Network"; string public constant symbol = "ADX"; uint8 public constant decimals = 18; // Mutable variables uint public totalSupply; mapping(address => uint) balances; mapping(address => mapping(address => uint)) allowed; event Approval(address indexed owner, address indexed spender, uint amount); event Transfer(address indexed from, address indexed to, uint amount); address public supplyController; address public immutable PREV_TOKEN; constructor(address supplyControllerAddr, address prevTokenAddr) public { supplyController = supplyControllerAddr; PREV_TOKEN = prevTokenAddr; } function balanceOf(address owner) external view returns (uint balance) { return balances[owner]; } function transfer(address to, uint amount) external returns (bool success) { balances[msg.sender] = balances[msg.sender].sub(amount); balances[to] = balances[to].add(amount); emit Transfer(msg.sender, to, amount); return true; } function transferFrom(address from, address to, uint amount) external returns (bool success) { balances[from] = balances[from].sub(amount); allowed[from][msg.sender] = allowed[from][msg.sender].sub(amount); balances[to] = balances[to].add(amount); emit Transfer(from, to, amount); return true; } function approve(address spender, uint amount) external returns (bool success) { allowed[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function allowance(address owner, address spender) external view returns (uint remaining) { return allowed[owner][spender]; } // Supply control function innerMint(address owner, uint amount) internal { totalSupply = totalSupply.add(amount); balances[owner] = balances[owner].add(amount); // Because of https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transfer-1 emit Transfer(address(0), owner, amount); } function mint(address owner, uint amount) external { require(msg.sender == supplyController, 'NOT_SUPPLYCONTROLLER'); innerMint(owner, amount); } function changeSupplyController(address newSupplyController) external { require(msg.sender == supplyController, 'NOT_SUPPLYCONTROLLER'); supplyController = newSupplyController; } // Swapping: multiplier is 10**(18-4) // NOTE: Burning by sending to 0x00 is not possible with many ERC20 implementations, but this one is made specifically for the old ADX uint constant PREV_TO_CURRENT_TOKEN_MULTIPLIER = 100000000000000; function swap(uint prevTokenAmount) external { innerMint(msg.sender, prevTokenAmount.mul(PREV_TO_CURRENT_TOKEN_MULTIPLIER)); SafeERC20.transferFrom(PREV_TOKEN, msg.sender, address(0), prevTokenAmount); } }
File 3 of 3: ADXSupplyController
// SPDX-License-Identifier: agpl-3.0 pragma solidity ^0.8.1; interface ISupplyController { function mintIncentive(address addr) external; function mintableIncentive(address addr) external view returns (uint); function mint(address token, address owner, uint amount) external; function changeSupplyController(IADXToken token, address newSupplyController) external; } interface IADXToken { function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); function approve(address spender, uint256 amount) external returns (bool); function balanceOf(address spender) external view returns (uint); function allowance(address owner, address spender) external view returns (uint); function totalSupply() external returns (uint); function supplyController() external view returns (ISupplyController); function changeSupplyController(address newSupplyController) external; function mint(address owner, uint amount) external; } contract ADXSupplyController { enum GovernanceLevel { None, Mint, All } uint public constant CAP = 150000000 * 1e18; // This amount was burned on purpose when migrating from Tom pool 2 (Staking with token 0xade) to Tom pool 3 (StakingPool with token 0xade) uint public immutable BURNED_MIN = 35000000 * 1e18; IADXToken public immutable ADX; mapping (address => uint8) public governance; // Some addresses (eg StakingPools) are incentivized with a certain allowance of ADX per year mapping (address => uint) public incentivePerSecond; // Keep track of when incentive tokens were last minted for a given addr mapping (address => uint) public incentiveLastMint; constructor(IADXToken token) { governance[msg.sender] = uint8(GovernanceLevel.All); ADX = token; } function changeSupplyController(address newSupplyController) external { require(governance[msg.sender] >= uint8(GovernanceLevel.All), "NOT_GOVERNANCE"); ADX.changeSupplyController(newSupplyController); } function setGovernance(address addr, uint8 level) external { require(governance[msg.sender] >= uint8(GovernanceLevel.All), "NOT_GOVERNANCE"); governance[addr] = level; } function setIncentive(address addr, uint amountPerSecond) external { require(governance[msg.sender] >= uint8(GovernanceLevel.All), "NOT_GOVERNANCE"); // no more than 1 ADX per second require(amountPerSecond < 1e18, "AMOUNT_TOO_LARGE"); incentiveLastMint[addr] = block.timestamp; incentivePerSecond[addr] = amountPerSecond; // AUDIT: pending incentive lost here } function innerMint(IADXToken token, address owner, uint amount) internal { uint totalSupplyAfter = token.totalSupply() + amount; require(totalSupplyAfter <= CAP + BURNED_MIN, "MINT_TOO_LARGE"); token.mint(owner, amount); } // Kept because it"s used for ADXLoyaltyPool function mint(IADXToken token, address owner, uint amount) external { require(governance[msg.sender] >= uint8(GovernanceLevel.Mint), "NOT_GOVERNANCE"); innerMint(token, owner, amount); } // Incentive mechanism function mintableIncentive(address addr) public view returns (uint) { return (block.timestamp - incentiveLastMint[addr]) * incentivePerSecond[addr]; } function mintIncentive(address addr) external { uint amount = mintableIncentive(addr); incentiveLastMint[addr] = block.timestamp; innerMint(ADX, addr, amount); } }