88 lines
4.3 KiB
JavaScript
88 lines
4.3 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
/**
|
||
|
|
* tron-refund-validate.cjs — validate R1 (self-refund after timeout) against a
|
||
|
|
* well-behaved standard TRC-20 (MockUsdt), isolating the lock contract's refund
|
||
|
|
* logic from the Nile faucet USDT's non-standard `transfer`.
|
||
|
|
*
|
||
|
|
* deploy MockUsdt → deploy UsdtLockNile(mock, me) → approve + lock 0.2 →
|
||
|
|
* wait past the deadline → refund → assert the locker's balance is restored.
|
||
|
|
*
|
||
|
|
* Run: node scripts/tron-refund-validate.cjs --yes
|
||
|
|
*/
|
||
|
|
const fs = require('fs');
|
||
|
|
const path = require('path');
|
||
|
|
const solc = require('solc');
|
||
|
|
const mod = require('tronweb');
|
||
|
|
const TronWeb = mod.TronWeb ?? mod.default ?? mod;
|
||
|
|
|
||
|
|
const ROOT = path.join(__dirname, '..');
|
||
|
|
const KEYS = JSON.parse(fs.readFileSync(path.join(ROOT, '.tron-nile-keys.json'), 'utf8'));
|
||
|
|
const FULL_HOST = 'https://nile.trongrid.io';
|
||
|
|
const BCH_RECIPIENT = '0x76a9149aa6baabb2bbb1d1940e4ec82b9640a7ab62b8dc88ac';
|
||
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||
|
|
const big = (v) => BigInt((v?.toString?.() ?? v));
|
||
|
|
|
||
|
|
function compile(file, name) {
|
||
|
|
const input = {
|
||
|
|
language: 'Solidity',
|
||
|
|
sources: { [file]: { content: fs.readFileSync(path.join(ROOT, 'contracts/tron', file), 'utf8') } },
|
||
|
|
settings: { optimizer: { enabled: true, runs: 200 }, evmVersion: 'istanbul', outputSelection: { '*': { '*': ['abi', 'evm.bytecode.object'] } } },
|
||
|
|
};
|
||
|
|
const out = JSON.parse(solc.compile(JSON.stringify(input)));
|
||
|
|
const errs = (out.errors || []).filter((e) => e.severity === 'error');
|
||
|
|
if (errs.length) { errs.forEach((e) => console.error(e.formattedMessage)); throw new Error('solc'); }
|
||
|
|
const c = out.contracts[file][name];
|
||
|
|
return { abi: c.abi, bytecode: c.evm.bytecode.object };
|
||
|
|
}
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
if (!process.argv.includes('--yes')) { console.log('Re-run with --yes.'); return; }
|
||
|
|
const tw = new TronWeb({ fullHost: FULL_HOST, privateKey: KEYS.privateKey });
|
||
|
|
tw.setAddress(KEYS.address);
|
||
|
|
const me = KEYS.address;
|
||
|
|
const chainNow = async () => Number(big((await tw.trx.getCurrentBlock()).block_header.raw_data.timestamp) / 1000n);
|
||
|
|
|
||
|
|
const mockArt = compile('MockUsdt.sol', 'MockUsdt');
|
||
|
|
const lockArt = compile('UsdtLockNile.sol', 'UsdtLockNile');
|
||
|
|
|
||
|
|
console.log('deploy MockUsdt (1000 mUSDT)…');
|
||
|
|
const mock = await tw.contract().new({ abi: mockArt.abi, bytecode: mockArt.bytecode, feeLimit: 1500_000_000, parameters: ['1000000000'] });
|
||
|
|
const mockAddr = tw.address.fromHex(mock.address);
|
||
|
|
console.log(' MockUsdt @ ' + mockAddr);
|
||
|
|
|
||
|
|
console.log('deploy UsdtLockNile(mock, me)…');
|
||
|
|
const lockI = await tw.contract().new({ abi: lockArt.abi, bytecode: lockArt.bytecode, feeLimit: 1500_000_000, parameters: [mockAddr, me] });
|
||
|
|
const lockAddr = tw.address.fromHex(lockI.address);
|
||
|
|
console.log(' lock @ ' + lockAddr);
|
||
|
|
|
||
|
|
const usdt = tw.contract(mockArt.abi, mockAddr);
|
||
|
|
const lock = tw.contract(lockArt.abi, lockAddr);
|
||
|
|
const bal = async () => big(await usdt.balanceOf(me).call());
|
||
|
|
|
||
|
|
console.log('approve + lock 0.2 mUSDT…');
|
||
|
|
await usdt.approve(lockAddr, '10000000').send({ feeLimit: 200_000_000, shouldPollResponse: false }); await sleep(7000);
|
||
|
|
const id = big(await lock.nextId().call());
|
||
|
|
const before = await bal();
|
||
|
|
const dl = (await chainNow()) + 60;
|
||
|
|
await lock.lock('200000', BCH_RECIPIENT, dl).send({ feeLimit: 300_000_000, shouldPollResponse: false }); await sleep(8000);
|
||
|
|
const afterLock = await bal();
|
||
|
|
console.log(` locked id ${id}: ${before} → ${afterLock}; waiting past deadline…`);
|
||
|
|
while ((await chainNow()) < dl + 4) await sleep(8000);
|
||
|
|
|
||
|
|
console.log('refund…');
|
||
|
|
const txid = await lock.refund(id.toString()).send({ feeLimit: 500_000_000, shouldPollResponse: false }); await sleep(12000);
|
||
|
|
const tx = await tw.trx.getTransaction(txid);
|
||
|
|
const ret = tx?.ret?.[0]?.contractRet;
|
||
|
|
const afterRefund = await bal();
|
||
|
|
const ok = ret === 'SUCCESS' && afterRefund === before;
|
||
|
|
console.log(` refund ${ret}: ${afterLock} → ${afterRefund} (restored=${ok})`);
|
||
|
|
|
||
|
|
console.log('\n=== RESULT ===');
|
||
|
|
console.log(` ${ok ? 'PASS' : 'FAIL'} R1 self-refund after timeout returns the locked token to the locker`);
|
||
|
|
console.log(ok
|
||
|
|
? 'VALIDATED — the refund safety valve works with a standard TRC-20. Non-custody holds; the Nile USDT failure was a token quirk (non-standard transfer), now handled by SafeERC20.'
|
||
|
|
: 'FAIL.');
|
||
|
|
if (!ok) process.exitCode = 1;
|
||
|
|
}
|
||
|
|
main().catch((e) => { console.error('FAILED:', e?.message ?? e); process.exitCode = 1; });
|