async function _runTests() {
const tests = [
async function simpleEvalTest() {
const acc = await (new World).exec([{eval: a => a + 1, expect: 2}], 1)
const result1 = await (new World).eval(a => a + 1, {expect: 2}, 1)
const result2 = await (new World).eval(a => a + 1, {expect: v => v == 2}, 1)
await assert(result1 == result2)
return {acc, result1, result2}
},
async function evalAccumulateTest(world) {
await assert(await world.exec([{eval: a => a + 1}, {eval: a => a * 2}], 7) == 16)
return {world}
},
async function evmLibTest(world, {uint}) {
const {
$blockNumber,
$setTime,
$increaseTime,
$$advanceBlocks
} = await world.env.library(EVMLib)
await world.env.assign({blocks: 333})
const block1 = await world.exec($blockNumber())
await world.exec($$advanceBlocks(+100))
const block2 = await world.exec($blockNumber())
await assert(block2 == (block1 + 100))
await world.exec($$advanceBlocks(-100))
const block3 = await world.exec($blockNumber())
await assert(block3 == (block2 - 100))
await world.exec($$advanceBlocks('blocks'))
const block4 = await world.exec($blockNumber())
await assert(block4 == (block3 + 333))
const offset = await world.exec($increaseTime(1111))
await assert(Number.isInteger(offset))
return {block1, block2, block3, block4}
},
async function etherscanTest(world, {address}) {
const env = await world.env.read(github())
const abi = await etherscan.getABI(address('Comptroller'))
const src = await etherscan.getSource(address('cBAT'))
const clone = await (new Env).loads(env.dumps())
await assert(areEqual(env.dumps(), clone.dumps()))
return {env, abi, src, clone}
},
async function basicTest(world, {val, address, uint, bool}) {
const a = address(0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc) // careful!
const b = address(0xb4e16d0168e52d35cacd2c6185b44281ec28c9dcn) // tricky
const c = address('0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc')
await assert(a != b)
await assert(b == c)
await assert(c == '0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc')
await assert(approx(99, 100) && !approx(98, 100) && !approx(99, 101))
await assert(approx(1, 2, 50) && !approx(1, 2, 49))
await assert(approx(0, 0))
await assert(approx(3n, 4n, 25n, 100n) && !approx(3n, 4n, 24n, 100n))
const _ = await world.env.assign({key: 10000000, key2: '0x1b', key3: '27'})
const value = val('key')
const unsigned = uint('key')
await assert(value == 10000000)
await assert(unsigned == 10000000n)
await assert(uint('key2') == 27)
await assert(uint('key3') == 27)
await assert(areEqual(unsigned, uint(uint(value))))
const accounts = await world.loadedAccounts;
const coinbaseInt = await num.hex(world.rpc('eth_coinbase'))
const lastBlock = await world.rpc('eth_getBlockByNumber', {params: ['latest', true]})
const nopCall = await world.call('notAMethod', {on: 0xabc, args: [3, 'ok', address(0)]})
const badSend = await world.send('notAMethod', {to: 123, args: [uint(4), bool(false)]})
return {a, b, c, coinbaseInt, lastBlock, nopCall, badSend}
},
async function forkAndLoadTest(world, {address}) {
const network = 'mainnet'
const env = await world.env.read(github(`networks/${network}.json`))
const forkInfo = await fork(network, {unlocked_accounts: [address('Timelock')]}, world)
const firstBal = await num.wei(world.web3.eth.getBalance(address('$0')))
const badSend = await world.send('_setBorrowPaused', {
to: address('Comptroller'),
args: [address('cBAT'), true],
revert: 'only pause guardian and admin can pause'
})
const badSend2 = await world.reverts(
{send: '_setBorrowPaused', to: 'Comptroller', args: [address('cBAT'), true]},
'only pause guardian and admin can pause'
)
return {forkInfo, firstBal, badSend, badSend2}
},
async function forkBlockTest(world) {
const network = 'mainnet', blockNumber = 10000000;
const env = await world.env.read(github(`networks/${network}.json`))
const forkInfo = await fork(network, {blockNumber}, world)
const lastBlock = await world.lastBlock(false)
await assert(lastBlock.number == blockNumber + 1)
return {env, forkInfo, lastBlock}
},
async function tailTest() {
const world = new World(new Env)
const env = await world.env.read(github(`networks/mainnet.json`))
const tail = await world.tail('cDAI', 'Mint', {blocks: 1000})
await assert(Object.keys(tail[0]) == 'Mint')
const tail2 = await world.tail('PriceData', 'Write', {blocks: 10000})
await assert(Object.keys(tail2[0]) == 'Write')
return {world, env, tail, tail2}
},
async function abiEncodeFunctionCallTest(world, {abi, address, uint, bool}) {
const traditional = abi.coder.encodeFunctionCall({
name: 'myMethod',
type: 'function',
inputs: [
{type: 'uint256', name: 'myNumber'},
{type: 'string', name: 'myString'}
]
}, ['2345675643', 'Hello!%'])
const simplified = abi.encodeFunctionCall('myMethod', [2345675643, 'Hello!%'])
await assert(traditional == simplified, 'abi encoding broken!')
const envelope = abi.shape([uint(3), bool(false), address(123)])
await assert(areEqual(envelope.components.map(c => c.type), ['uint256', 'bool', 'address']))
return {traditional, simplified, envelope}
},
async function abiEncodeDecodeTest(world, {abi, address, array, string, uint}) {
const i = [address(0), uint(0), array(string)(["welcome to my world"])]
const s = abi.shape(i)
const e = abi.encode(i)
const d = abi.decode(e, s)
await assert(areEqual(d, i))
return {i, s, e, d}
},
async function abiArrayTest(world, {abi, address, array, bytes, uint}) {
await assert(uint(2, 8).abiType == 'uint8')
await assert(abi.shape(array('string')(['s'])).type == 'string[]')
await assert(abi.shape(array('MyType')([{}])).type == 'MyType[]')
const e1a = abi.coder.encodeParameters(['uint8[]','bytes32'], [['34','255'], '0x324567fff'])
const e1b = abi.encode([array('uint8')(['34', '255']), bytes('0x324567fff', 32)])
await assert(e1a == e1b, 'abi encoding broken')
const e2a = abi.coder.encodeParameters(['uint8[]'], [['34','255']])
const e2b = abi.encode([array('uint8')(['34', '255'])])
await assert(e2a == e2b, 'abi encoding broken')
const e3a = abi.coder.encodeParameter('uint8[]', ['34','255'])
const e3b = abi.encodeOne(array('uint8')([34, 255n]))
await assert(e3a == e3b, 'abi encoding broken')
const z4 = [
array('address')([]),
array('bytes')([abi.encode([address(123)])])
]
const e4b = abi.encode(z4)
const d4b = abi.decode(e4b, abi.shape(z4))
await assert(areEqual(d4b, z4))
return {e1a, e1b, e2a, e2b, e3a, e3b, z4, e4b, d4b}
},
async function abiStructTest(world, {abi, array, uint, Struct}) {
const ParentShape = {
p1: 'uint256',
p2: 'uint256',
Child: {
p1: 'uint256',
p2: 'uint256'
}
}
const Parent = new Struct('Parent', ParentShape) // define type
const ParentABI = {
type: 'tuple',
components: [
{type: 'uint256', name: 'p1'},
{type: 'uint256', name: 'p2',},
{type: 'tuple',
name: 'Child',
components: [
{type: 'uint256', name: 'p1'},
{type: 'uint256', name: 'p2'}]}]}
await assert(areEqual(abi.resolve(abi.shape(Parent({}))), ParentABI), 'abi shape resolve broken')
const classic = abi.coder.encodeParameters(
['uint8[]', {Parent: ParentShape}],
[['34','255'], {p1: '42', p2: '56', Child: {p1: '45', p2: '78'}}]
)
const modern = abi.encode([
array('uint8')([34, '255']),
Parent({p1: 42, p2: 56, Child: {p1: '45', p2: 78n}})
])
await assert(classic == modern, `abi encoding broken`)
const expect = [[34, 255n], ['42', '56', ['45', '78']]]
const dc = abi.coder.decodeParameters(['uint8[]', {Parent: ParentShape}], classic)
const dm = abi.decode(modern, ['uint8[]', ParentABI])
await assert(!areEqual(dc, dm), `should be slightly different structures`)
await assert(areEqual(dm, expect), `abi decoding broken`)
const Observation = new Struct('Observation', {
timestamp: 'uint256',
acc: 'uint256'
})
const data = ["1595543220","73553348765004598352779970224"]
const cast = abi.cast(data, Observation)
await assert(areEqual(abi.strip(cast), {timestamp: 1595543220n, acc: 73553348765004598352779970224n}))
return {classic, modern, dc, dm, cast}
},
async function abiStructTest2(world, {abi, address, array, bytes, Enum, Struct}) {
const PriceSource = new Enum('PriceSource', ['FIXED_ETH', 'FIXED_USD', 'REPORTER'])
const TokenConfig = new Struct('TokenConfig', {
cToken: 'address',
underlying: 'address',
symbolHash: 'bytes32',
baseUnit: 'uint256',
priceSource: 'PriceSource',
fixedPrice: 'uint256',
uniswapMarket: 'address',
isUniswapReversed: 'bool'
})
const {PriceSource: p, TokenConfig: t} = world.env.bindings()
await assert(p === PriceSource)
await assert(t === TokenConfig)
const t0 = TokenConfig(0)
const t1 = TokenConfig({symbolHash: bytes('0x324567fff', 32)})
const e1 = abi.encodeOne(t1)
const d1 = abi.decodeOne(e1, TokenConfig)
await assert(areEqual(d1, abi.strip(t1)))
// TODO: deploy(bin, {args})
/*
await world.deploy(contractBin, {as: 'abiStructTest2', args: [
address(0),
address(1),
exp(.2),
1800,
array('TokenConfig')([])
]})
*/
return {PriceSource, TokenConfig, t0, e1, d1}
},
async function abiBytesTest(world, {abi, array, bytes, keccak}) {
const e4a = abi.coder.encodeParameters(['bytes32'], ['0x324567fff'])
const e4b = abi.encode([bytes('0x324567fff', 32)])
const e4c = abi.encode([bytes(0x324567fff, 32)])
await assert(e4a == e4b)
await assert(e4a == e4c)
const e5a = abi.coder.encodeParameters(['bytes16'], ['0x32456000000000000000000000000000'])
const e5b = abi.encode([bytes('0x32456000000000000000000000000000', 16)])
const e5c = abi.encode([bytes('0x32456', 16)])
const e5d = abi.encode([bytes(0x32456, 16)])
await assert(e5a == e5b)
await assert(e5a == e5c)
await assert(e5a == e5c)
const e6a = abi.coder.encodeParameters(['bytes32'], ['0x324567fff'])
const e6b = abi.encode([bytes('0x324567fff', 32)])
await assert(e6a == e6b)
await assert(keccak('abc') == '0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45')
return {abi, bytes, e4a, e4b, e4c, e5a, e5b, e5c, e5d, e6a, e6b}
},
async function abiMaybeTest(world, {abi, address}) {
const a = maybe(address)
const s = maybe('string')
const d1 = abi.decode('0x', a)
const d2 = abi.decode('0x', s)
await assert(d1 === undefined)
await assert(d2 === undefined)
const a3 = address(123)
const d3 = abi.decodeOne(abi.encodeOne(a3), a)
await assert(d3 == a3)
const s4 = 'hello'
const d4 = abi.decode(abi.encode([s4]), [s])
await assert(d4 == s4)
return {a, s, d1, d2, a3, d3, s4, d4}
},
async function zeroTypesTest(world, {address, array, bool, bytes, int, uint, Enum, Struct}) {
const anEnum = new Enum('anEnum', ['price'])
const aStruct = new Struct('aStruct', {})
return {
address: address(0),
array_address: array(address)(0),
array_uint: array('uint')(0),
bool: bool(0),
bytes: bytes(0),
int: int(0),
uint: uint(0),
enum: anEnum(0),
struct: aStruct(0)
}
},
async function fullForkTest(world, {array, address, bytes, string, uint}) {
const network = 'mainnet', blockNumber = 10517750, unlocked_accounts = ['BAT'];
const env = await world.env.read(github(`networks/${network}.json`))
const forkInfo = await world.fork(network, {blockNumber, unlocked_accounts, useProxy: true})
const result = await world.exec([
{call: 'enterMarkets', on: 'Comptroller', args: [array(address)([address('cETH')])], returns: array(uint), from: '0xF977814e90dA44bFA03b6295A0616a897441aceC'},
{send: 'propose', to: 'GovernorAlpha', args: [
array(address)([]),
array(uint)([]),
array(string)([]),
array(bytes)([]),
string("proposal description")
], revert: "GovernorAlpha::propose: proposer votes below proposal threshold"},
{rpc: 'eth_getBlockByNumber', params: ['latest', true], expect: b => b.number == 10517752},
{send: 'approve', to: 'BAT', from: 'BAT', args: [address('cBAT'), exp(10000)], gasPrice: 0},
{call: 'balanceOf', on: 'cBAT', args: [address('BAT')], returns: uint, expect: 0},
{send: 'mint', to: 'cBAT', from: 'BAT', args: [exp(100)], gasPrice: 0, emits: ['Mint', '!Failure']},
{call: 'balanceOf', on: 'cBAT', args: [address('BAT')], returns: uint, expect: 490735421750},
{call: 'balanceOf', on: 'SAI', args: [address('cSAI')], returns: num.wei, expect: x => ~~x == 341006},
{send: 'redeem', to: 'cBAT', from: 'BAT', args: [exp(100)], gasPrice: 0, emits: 'Failure'}
])
const logs = await world.emits([
{send: 'approve', to: 'BAT', from: 'BAT', args: [address('cBAT'), exp(10000)]},
{send: 'redeem', to: 'cBAT', from: 'BAT', args: [exp(100)]}
], {Failure: {error: 9, info: 46 , detail: 3}})
const {prior, post} = await world.invariant(
{call: 'balanceOf', on: 'BAT', args: [address('BAT')], returns: uint},
{send: 'mint', to: 'cBAT', from: 'BAT', args: [exp(100)]},
exp(-100))
const batBin = await world.contractCode('cBAT')
const nilBin = await world.contractCode(address(0))
await assert(batBin && batBin != nilBin)
await assert(nilBin == "0x")
return {world, result, logs, prior, post, batBin, nilBin}
},
async function deployTest(world, {address}) {
const tiny = {
name: 'Tiny',
bin: '608060405234801561001057600080fd5b5060405161016b38038061016b8339818101604052602081101561003357600080fd5b50516001600160a01b038116610090576040805162461bcd60e51b815260206004820152601660248201527f782063616e6e6f74206265206164647265737328302900000000000000000000604482015290519081900360640190fd5b5060cc8061009f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806326c3b22814602d575b600080fd5b604760048036036020811015604157600080fd5b50356059565b60408051918252519081900360200190f35b6040805182815290516000917f381d78e5942c3f38c806fe97195e3d58555b5c63329a19f958bc104a95c9712f919081900360200190a15080019056fea264697066735822122001bd900349cc18b8054c26ba730cc8ca3355280e24ff51260afc3ead49a7d9d464736f6c634300060a0033'
}
const d0 = await world.deploy(tiny, {args: [], revert: ''})
const d1 = await world.deploy(tiny, {args: [address(0)], revert: 'x cannot be address(0)'})
const d2 = await world.deploy(tiny, {args: [address(1)]})
const c1 = await world.call('tiny', {on: d2.address, args: [1]})
await assert(c1 == 2)
const s1 = await world.send('tiny', {to: d2.address, args: [1], emits: '!TinyLog'}) // no abi!
return {world, tiny, d0, d1, d2, c1, s1}
}
];
const results = {}
for (const test of tests) {
const env = await GanacheEnv()
const filter = t => {
if (t.name == 'fullForkTest')
return false; // change me if forking changes, otherwise skip (requires proxy)
return true; // edit me (e.g. t.name == 'evmLib')
}
results[test.name] = filter(test) ? await test(new World(env), env.bindings()) : 'skipped'
}
return results;
}