🧻

Log Rollup

2024/05/04に公開

ログをロールアップすることで、安くL1を使用するアイデア.

例:

  1. 「太郎くんがaliceに1000eth送る。」というeventをemitする。
  2. 「aliceは智くんに90ethを送る。」というeventをemitする。
  3. マイナーは、ログを集計するコントラクトを実行し、JSCODEがハードコードされているChainlinkを動かすコントラクトを実行する。
  4. ERC20コントラクトの間で、トークンの移動を容易にするコントラクトであるRouterを設置し、それとやりとりすることで、トークンの移動を実現する・
const ethers = await import('npm:ethers@5.7.0');

const API_KEY = secrets.scanApiKey;
const CONTRACT_ADDRESS = '0x6E3c495d83E47e41A6c37A49F89e8f6B459db1E7';
const START_BLOCK = args[0]; // from contract
const provider = new ethers.providers.JsonRpcProvider(secrets.rpc);
const erc20Contract = new ethers.Contract(
  CONTRACT_ADDRESS, 
  ['event TransferLog(address indexed from, address indexed to, uint256 value)', 
   'function setHashForBatch(bytes calldata hash, uint256 lastBlockNumber)'], 
  provider
);
const wallet = new ethers.Wallet(secrets.privateKey, provider);
const END_BLOCK = await provider.getBlockNumber();
const TOPIC = "0x2083e82b9793082f4e269de9ab01b8b76ee80ee34051ce54d84671158bede8df";
const BASE_URL = 'https://api-sepolia.basescan.org/api';

const response = await Functions.makeHttpRequest({
  url: BASE_URL,
  method: 'GET',
  params: {
    module: 'logs',
    action: 'getLogs',
    fromBlock: START_BLOCK,
    toBlock: END_BLOCK,
    address: CONTRACT_ADDRESS,
    topic0: TOPIC,
    apikey: API_KEY
  },
  responseType: 'json'
});

if (!(response.status === 200 && response.data.status === '1')) {
  throw new Error(`Error retrieving logs: ${response.data.message || response.data}`);
}

console.log('Logs retrieved successfully:', response.data.result);

const iface = new ethers.utils.Interface([
  'event TransferLog(address indexed from, address indexed to, uint256 value, bytes signature, string message, uint256 nonce)'
]);

const routerTransferFrom = response.data.result
  .map(log => iface.parseLog(log).args)
  .map(({ from, to, value, signature, message, nonce }) => ({ from, to, value, signature, message, nonce }));

console.log('Parsed event args:', routerTransferFrom);

const routerTransferFromHash = ethers.utils.keccak256(
  ethers.utils.defaultAbiCoder.encode(
    ['tuple(address from, address to, uint256 value, bytes signature, string message, uint256 nonce)[]'], 
    [routerTransferFrom]
  )
);

console.log('Balance Dict Hash:', routerTransferFromHash);

await erc20Contract.setHashForBatch(routerTransferFromHash, END_BLOCK);

return Functions.encodeString(`Balance Dict Hash: ${routerTransferFromHash}`);


// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import {FunctionsClient} from "@chainlink/contracts@1.1.1/src/v0.8/functions/v1_0_0/FunctionsClient.sol";
import {ConfirmedOwner} from "@chainlink/contracts@1.1.1/src/v0.8/shared/access/ConfirmedOwner.sol";
import {FunctionsRequest} from "@chainlink/contracts@1.1.1/src/v0.8/functions/v1_0_0/libraries/FunctionsRequest.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

interface IRouter {
    struct RouterTransfer {
        address from;
        address to;
        uint256 value;
    }
    function batch(RouterTransfer[] calldata data) external;
    function depositAmount(address user) external returns(uint256);
    function mining(address miner) external;
}

contract AI is FunctionsClient, ConfirmedOwner {
    using FunctionsRequest for FunctionsRequest.Request;
    using Strings for uint256;


    bytes32 public s_lastRequestId;
    bytes public s_lastResponse;
    bytes public s_lastError;

    // Custom error type
    error UnexpectedRequestID(bytes32 requestId);

    // Event to log responses
    event Response(
        bytes32 indexed requestId,
        bytes response,
        bytes err
    );

    address router = 0xf9B8fc078197181C841c296C876945aaa425B278;
    address tokenRouter;

    uint32 gasLimit = 300000;

    bytes32 donID =
        0x66756e2d626173652d7365706f6c69612d310000000000000000000000000000;

    string public source = "const ethers = await import('npm:ethers@5.7.0');const API_KEY = secrets.scanApiKey;const CONTRACT_ADDRESS = '0x6E3c495d83E47e41A6c37A49F89e8f6B459db1E7';const START_BLOCK = args[0];const provider = new ethers.providers.JsonRpcProvider(secrets.rpc);const erc20Contract = new ethers.Contract(CONTRACT_ADDRESS, ['event TransferLog(address indexed from, address indexed to, uint256 value)','function setHashForBatch(bytes calldata hash, uint256 lastBlockNumber)'], provider);const wallet = new ethers.Wallet(secrets.privateKey, provider);const END_BLOCK = await provider.getBlockNumber();const TOPIC = '0x2083e82b9793082f4e269de9ab01b8b76ee80ee34051ce54d84671158bede8df';const BASE_URL = 'https://api-sepolia.basescan.org/api';const response = await Functions.makeHttpRequest({url: BASE_URL,method: 'GET',params: {module: 'logs',action: 'getLogs',fromBlock: START_BLOCK,toBlock: END_BLOCK,address: CONTRACT_ADDRESS,topic0: TOPIC,apikey: API_KEY},responseType: 'json'});if(!(response.status === 200 && response.data.status === '1')){throw new Error(`Error retrieving logs: ${response.data.message || response.data}`);}console.log('Logs retrieved successfully:', response.data.result);const iface = new ethers.utils.Interface(['event TransferLog(address indexed from, address indexed to, uint256 value, bytes signature, string message, uint256 nonce)']);const routerTransferFrom = response.data.result.map(log => iface.parseLog(log).args).map(({ from, to, value, signature, message, nonce }) => ({ from, to, value, signature, message, nonce }));console.log('Parsed event args:', routerTransferFrom);const routerTransferFromHash = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(['tuple(address from, address to, uint256 value, bytes signature, string message, uint256 nonce)[]'], [routerTransferFrom]));console.log('Balance Dict Hash:', routerTransferFromHash);await erc20Contract.setHashForBatch(routerTransferFromHash, END_BLOCK);return Functions.encodeString(`Balance Dict Hash: ${routerTransferFromHash}`);";
    uint256 lastBlockNumber;
    bytes32 public routerTransferBatchHash;


    event TransferLog(address indexed from, address indexed to, uint256 value);

    function emitLog(address to, uint256 value) external {
        require(IRouter(tokenRouter).depositAmount(msg.sender) >= value, "insufficient");
        emit TransferLog(msg.sender, to, value);
    }

    constructor(address _tokenRouter) FunctionsClient(router) ConfirmedOwner(msg.sender) {
        lastBlockNumber = block.timestamp;
        tokenRouter = _tokenRouter;
    }

    function setHashForBatch(uint256 blockNumber, bytes32 hash) external {
        lastBlockNumber = blockNumber;
        routerTransferBatchHash = hash;
    }

    function sendBatch(IRouter.RouterTransfer[] calldata routerTransfer) external {
        require(keccak256(abi.encode(routerTransfer)) == routerTransferBatchHash, "wrong data");
        IRouter(tokenRouter).batch(routerTransfer);
    }

    
    function sendRequest(
        bytes memory encryptedSecretsUrls,
        uint64 subscriptionId
    ) external returns(bytes32) {
        FunctionsRequest.Request memory req;
        req.addSecretsReference(encryptedSecretsUrls);
        req.initializeRequestForInlineJavaScript(source);
        string[] memory args;
        args[1] = block.number.toString();
        args[2] = lastBlockNumber.toString();
        req.setArgs(args);
        IRouter(tokenRouter).mining(msg.sender);
        s_lastRequestId = _sendRequest(
            req.encodeCBOR(),
            subscriptionId,
            gasLimit,
            donID
        );
        return s_lastRequestId;
    }

    event Mining(address indexed miner,string indexed miningType, bytes32 indexed requestId, bytes response, bool success);

    struct Distribute {
        uint256 people;
        uint256 industory;
        uint256 other;
    }

    function fulfillRequest(
        bytes32 requestId,
        bytes memory response,
        bytes memory err
    ) internal override {

        if (s_lastRequestId != requestId) {
            revert UnexpectedRequestID(requestId);
        }

        s_lastResponse = response;
        s_lastError = err;

        emit Response(requestId, s_lastResponse, s_lastError);
    }


}

https://gist.github.com/0xjint/70c1d829b48e9a7033a2698595208f2b

Discussion