Solidity: Approvals and Token Handling
This page covers how your contracts prepare ERC‑20 balances and allowances for interacting with the DecentralizedOracle. No governance or deployment details are included.
Key facts:
- The oracle pulls tokens via
transferFromon:createQuery(...)— pulls the fixed creation deposit.voteOnQuery(...)— pulls the specified collateral.
- Your contract must hold tokens and approve the oracle before calling these functions.
- Time is block-based; no special token mechanics occur during resolution beyond payout from the oracle’s holdings.
Funding your contract
Your contract must first receive tokens. Typical patterns:
- External EOAs
transfertokens to your contract address. - Another system contract transfers tokens in a controlled flow.
Example (outside scope of oracle):
// Users send tokens directly to this contract address.
// The contract should expose a withdrawal function with access control if needed.
Approving the oracle
Approve once with a sufficiently large allowance, then reuse for multiple oracle operations.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IOracle {
function createQuery(uint256, string calldata, string calldata, uint8) external returns (uint256);
function voteOnQuery(uint256, uint8[] calldata, uint256) external;
}
contract OracleApprovals {
IOracle public immutable oracle;
IERC20 public immutable token;
constructor(address oracle_, address token_) {
oracle = IOracle(oracle_);
token = IERC20(token_);
}
/// @notice Approve the oracle to pull up to `amount` tokens from this contract.
function approveOracle(uint256 amount) external {
// Ensure this contract already holds tokens (fund first).
require(token.approve(address(oracle), amount), "approve failed");
}
}
Considerations:
- Some tokens (non‑standard ERC‑20s) require setting allowance to 0 before updating. If the token enforces that pattern, call
approve(oracle, 0)first, then approve the new amount. - Persist a generous allowance to minimize repeated approvals, or implement a per‑tx approval flow if stricter control is necessary (adds gas and complexity).
Create and vote using the approved allowance
Once approved, the contract can call oracle methods that pull funds:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract OracleUsage {
IOracle public immutable oracle;
constructor(address oracle_) {
oracle = IOracle(oracle_);
}
function openQuery(
uint256 startOffset,
string calldata question,
string calldata metadata,
uint8 totalOptions
) external returns (uint256 queryId) {
// Assumes allowance covers the required creation deposit
queryId = oracle.createQuery(block.number + startOffset, question, metadata, totalOptions);
}
function voteSingleOption(uint256 queryId, uint8 option, uint256 collateral) external {
// Assumes allowance covers `collateral`
uint8[] memory opts = new uint8[](1);
opts[0] = option;
oracle.voteOnQuery(queryId, opts, collateral);
}
}
Allowance management patterns
- One‑time large allowance: simplest UX and gas‑efficient. Risk: if oracle address is compromised (contract itself should be immutable), you have over‑approved. Typically acceptable for audited/immutable contracts.
- Per‑call approval (strict): set allowance to the exact amount needed each time. Safer but higher gas and UX complexity. Also be aware of tokens requiring zero‑first semantics.
Security checklist
- Ensure only authorized actors can call functions that trigger spending (e.g.,
onlyOwner, role checks). - Provide a withdrawal method for stuck tokens with access control.
- Consider events for internal bookkeeping (e.g., “OracleAllowanceUpdated” or “QueryOpened”).
- Avoid approving unbounded allowances if you expect to rotate oracle addresses in the future.
Next Pages
Back to: