Add ABI diff logic (#598)
parent
b2d4a667d5
commit
ac0618c34f
@ -0,0 +1,99 @@ |
||||
const ethersABI = require("@ethersproject/abi"); |
||||
const difflib = require('difflib'); |
||||
|
||||
class AbiUtils { |
||||
|
||||
diff(orig={}, cur={}){ |
||||
let plus = 0; |
||||
let minus = 0; |
||||
|
||||
const unifiedDiff = difflib.unifiedDiff( |
||||
orig.humanReadableAbiList, |
||||
cur.humanReadableAbiList, |
||||
{ |
||||
fromfile: orig.contractName, |
||||
tofile: cur.contractName, |
||||
fromfiledate: `sha: ${orig.sha}`, |
||||
tofiledate: `sha: ${cur.sha}`, |
||||
lineterm: '' |
||||
} |
||||
); |
||||
|
||||
// Count changes (unified diff always has a plus & minus in header);
|
||||
if (unifiedDiff.length){ |
||||
plus = -1; |
||||
minus = -1; |
||||
} |
||||
|
||||
unifiedDiff.forEach(line => { |
||||
if (line[0] === `+`) plus++; |
||||
if (line[0] === `-`) minus++; |
||||
}) |
||||
|
||||
return { |
||||
plus, |
||||
minus, |
||||
unifiedDiff |
||||
} |
||||
} |
||||
|
||||
toHumanReadableFunctions(contract){ |
||||
const human = []; |
||||
const ethersOutput = new ethersABI.Interface(contract.abi).functions; |
||||
const signatures = Object.keys(ethersOutput); |
||||
|
||||
for (const sig of signatures){ |
||||
const method = ethersOutput[sig]; |
||||
let returns = ''; |
||||
|
||||
method.outputs.forEach(output => { |
||||
(returns.length) |
||||
? returns += `, ${output.type}` |
||||
: returns += output.type; |
||||
}); |
||||
|
||||
let readable = `${method.type} ${sig} ${method.stateMutability}`; |
||||
|
||||
if (returns.length){ |
||||
readable += ` returns (${returns})` |
||||
} |
||||
|
||||
human.push(readable); |
||||
} |
||||
|
||||
return human; |
||||
} |
||||
|
||||
toHumanReadableEvents(contract){ |
||||
const human = []; |
||||
const ethersOutput = new ethersABI.Interface(contract.abi).events; |
||||
const signatures = Object.keys(ethersOutput); |
||||
|
||||
for (const sig of signatures){ |
||||
const method = ethersOutput[sig]; |
||||
const readable = `${ethersOutput[sig].type} ${sig}`; |
||||
human.push(readable); |
||||
} |
||||
|
||||
return human; |
||||
} |
||||
|
||||
generateHumanReadableAbiList(_artifacts, sha){ |
||||
const list = []; |
||||
if (_artifacts.length){ |
||||
for (const item of _artifacts){ |
||||
const fns = this.toHumanReadableFunctions(item); |
||||
const evts = this.toHumanReadableEvents(item); |
||||
const all = fns.concat(evts); |
||||
list.push({ |
||||
contractName: item.contractName, |
||||
sha: sha, |
||||
humanReadableAbiList: all |
||||
}) |
||||
} |
||||
} |
||||
return list; |
||||
} |
||||
} |
||||
|
||||
module.exports = AbiUtils; |
@ -0,0 +1,38 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Old { |
||||
uint public y; |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
|
||||
function b() external { |
||||
bool x = true; |
||||
} |
||||
|
||||
function c() external { |
||||
bool x = true; |
||||
} |
||||
} |
||||
|
||||
contract New { |
||||
uint public y; |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
|
||||
function b() external { |
||||
bool x = true; |
||||
} |
||||
|
||||
function c() external { |
||||
bool x = true; |
||||
} |
||||
|
||||
function d() external { |
||||
bool x = true; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,23 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Old { |
||||
uint y; |
||||
|
||||
event Evt(uint x, bytes8 y); |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
} |
||||
|
||||
contract New { |
||||
uint y; |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
|
||||
event aEvt(bytes8); |
||||
event _Evt(bytes8 x, bytes8 y); |
||||
} |
||||
|
@ -0,0 +1,34 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Old { |
||||
uint y; |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
|
||||
function b() external { |
||||
bool x = true; |
||||
} |
||||
|
||||
function c() external { |
||||
bool x = true; |
||||
} |
||||
} |
||||
|
||||
contract New { |
||||
uint y; |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
|
||||
function b() external { |
||||
bool x = true; |
||||
} |
||||
|
||||
function c() external { |
||||
bool x = true; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,33 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Old { |
||||
uint y; |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
|
||||
function b() external { |
||||
bool x = true; |
||||
} |
||||
|
||||
function c() external { |
||||
bool x = true; |
||||
} |
||||
} |
||||
|
||||
contract New { |
||||
uint y; |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
|
||||
function b(bytes8 z) external { |
||||
bool x = true; |
||||
} |
||||
|
||||
function c(uint q, uint r) external { |
||||
bool x = true; |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Old { |
||||
uint y; |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
|
||||
function b() external { |
||||
bool x = true; |
||||
} |
||||
|
||||
function c() external { |
||||
bool x = true; |
||||
} |
||||
} |
||||
|
||||
contract New { |
||||
uint y; |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
|
||||
function b() external { |
||||
bool x = true; |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Old { |
||||
uint y; |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
|
||||
function b() external { |
||||
bool x = true; |
||||
} |
||||
|
||||
function c() external { |
||||
bool x = true; |
||||
} |
||||
} |
||||
|
||||
contract New { |
||||
uint y; |
||||
|
||||
function c() external { |
||||
bool x = true; |
||||
} |
||||
|
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
|
||||
function b() external { |
||||
bool x = true; |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Old { |
||||
function a() public view returns (uint) { |
||||
return 1; |
||||
} |
||||
} |
||||
|
||||
contract New { |
||||
function a() public view returns (bool) { |
||||
return true; |
||||
} |
||||
|
||||
function e() public view returns (uint8[2] memory) { |
||||
return [5,7]; |
||||
} |
||||
|
||||
function f() public view returns (uint8[2] memory, uint) { |
||||
return ([5,7], 7); |
||||
} |
||||
|
||||
function g() public view returns (uint8[3] memory) { |
||||
return [5,7,8]; |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Old { |
||||
function a() public { |
||||
bool x = true; |
||||
} |
||||
} |
||||
|
||||
contract New { |
||||
function a() public view returns (bool) { |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,128 @@ |
||||
const assert = require('assert'); |
||||
const util = require('./../util/util.js'); |
||||
const Api = require('./../../lib/api') |
||||
|
||||
describe('abi diffs', function(){ |
||||
const api = new Api(); |
||||
|
||||
function setUp(source){ |
||||
const abis = util.getDiffABIs(source); |
||||
const orig = api.abiUtils.generateHumanReadableAbiList([abis.original], abis.original.sha); |
||||
const cur = api.abiUtils.generateHumanReadableAbiList([abis.current], abis.current.sha); |
||||
return api.abiUtils.diff(orig[0], cur[0]); |
||||
} |
||||
|
||||
function validate(result, expectPlus, expectMinus, expectDiff){ |
||||
assert.equal(result.plus, expectPlus); |
||||
assert.equal(result.minus, expectMinus); |
||||
assert.deepEqual(result.unifiedDiff, expectDiff); |
||||
} |
||||
|
||||
it('when methods are added', function() { |
||||
const expectPlus = 1; |
||||
const expectMinus = 0; |
||||
const expectDiff = [ |
||||
"--- Test\tsha: d8b26d8", |
||||
"+++ Test\tsha: e77e29d", |
||||
"@@ -1,4 +1,5 @@", |
||||
" function a() nonpayable", |
||||
" function b() nonpayable", |
||||
" function c() nonpayable", |
||||
"+function d() nonpayable", |
||||
" function y() view returns (uint256)" |
||||
]; |
||||
|
||||
validate(setUp('diff/addition'), expectPlus, expectMinus, expectDiff); |
||||
}); |
||||
|
||||
it('when there are events', function() { |
||||
const expectPlus = 2; |
||||
const expectMinus = 1; |
||||
const expectDiff = [ |
||||
"--- Test\tsha: d8b26d8", |
||||
"+++ Test\tsha: e77e29d", |
||||
"@@ -1,2 +1,3 @@", |
||||
" function a() nonpayable", |
||||
"-event Evt(uint256,bytes8)", |
||||
"+event _Evt(bytes8,bytes8)", |
||||
"+event aEvt(bytes8)" |
||||
]; |
||||
|
||||
validate(setUp('diff/events'), expectPlus, expectMinus, expectDiff); |
||||
}); |
||||
|
||||
it('when parameters change', function() { |
||||
const expectPlus = 2; |
||||
const expectMinus = 2; |
||||
const expectDiff = [ |
||||
"--- Test\tsha: d8b26d8", |
||||
"+++ Test\tsha: e77e29d", |
||||
"@@ -1,3 +1,3 @@", |
||||
" function a() nonpayable", |
||||
"-function b() nonpayable", |
||||
"-function c() nonpayable", |
||||
"+function b(bytes8) nonpayable", |
||||
"+function c(uint256,uint256) nonpayable" |
||||
]; |
||||
|
||||
validate(setUp('diff/param-change'), expectPlus, expectMinus, expectDiff); |
||||
}); |
||||
|
||||
it('when there is no change', function() { |
||||
const expectPlus = 0; |
||||
const expectMinus = 0; |
||||
const expectDiff = []; |
||||
validate(setUp('diff/no-change'), expectPlus, expectMinus, expectDiff); |
||||
}); |
||||
|
||||
it('when methods are removed', function() { |
||||
const expectPlus = 0; |
||||
const expectMinus = 1; |
||||
const expectDiff = [ |
||||
'--- Test\tsha: d8b26d8', |
||||
'+++ Test\tsha: e77e29d', |
||||
'@@ -1,3 +1,2 @@', |
||||
' function a() nonpayable', |
||||
' function b() nonpayable', |
||||
'-function c() nonpayable' |
||||
]; |
||||
|
||||
validate(setUp('diff/removal'), expectPlus, expectMinus, expectDiff); |
||||
}); |
||||
|
||||
it('when methods are reordered', function() { |
||||
const expectPlus = 0; |
||||
const expectMinus = 0; |
||||
const expectDiff = []; |
||||
validate(setUp('diff/reorder'), expectPlus, expectMinus, expectDiff); |
||||
}); |
||||
|
||||
it('when return signatures change', function() { |
||||
const expectPlus = 4; |
||||
const expectMinus = 1; |
||||
const expectDiff = [ |
||||
'--- Test\tsha: d8b26d8', |
||||
'+++ Test\tsha: e77e29d', |
||||
'@@ -1 +1,4 @@', |
||||
'-function a() view returns (uint256)', |
||||
'+function a() view returns (bool)', |
||||
'+function e() view returns (uint8[2])', |
||||
'+function f() view returns (uint8[2], uint256)', |
||||
'+function g() view returns (uint8[3])' |
||||
]; |
||||
validate(setUp('diff/return-sig'), expectPlus, expectMinus, expectDiff); |
||||
}); |
||||
|
||||
it('when state modifiablility changes', function() { |
||||
const expectPlus = 1; |
||||
const expectMinus = 1; |
||||
const expectDiff = [ |
||||
'--- Test\tsha: d8b26d8', |
||||
'+++ Test\tsha: e77e29d', |
||||
'@@ -1 +1 @@', |
||||
'-function a() nonpayable', |
||||
'+function a() view returns (bool)' |
||||
]; |
||||
validate(setUp('diff/state-mod-change'), expectPlus, expectMinus, expectDiff); |
||||
}); |
||||
}); |
Loading…
Reference in new issue